Package has been replaced by aedart/athenaeum
A variation / interpretation of the Data Transfer Object (DTO) design pattern (Distribution Pattern). A DTO is nothing more than an object that can hold some data. Most commonly it is used for for transporting that data between systems, e.g. a client and a server.
This package provides an abstraction for such DTOs.
If you don't know about DTOs, I recommend you to read Martin Fowler's description of DTO, and perhaps perform a few Google searches about this topic.
- When to use this
- How to install
- Quick start
- Advanced usage
- Contribution
- Acknowledgement
- Versioning
- License
- When there is a strong need to interface DTOs, e.g. what properties must be available via getters and setters
- When you need to encapsulate data that needs to be communicated between systems and or component instances
Nevertheless, using DTOs can / will increase complexity of your project. Therefore, you should only use it, when you are really sure that you need them.
composer require aedart/dto
This package uses composer. If you do not know what that is or how it works, I recommend that you read a little about, before attempting to use this package.
Start off by creating an interface for your DTO. Below is an example for a simple Person interface
<?php
use Aedart\DTO\Contracts\DataTransferObject as DataTransferObjectInterface;
interface PersonInterface extends DataTransferObjectInterface
{
/**
* Set the person's name
*
* @param string|null $name
*/
public function setName(?string $name);
/**
* Get the person's name
*
* @return string
*/
public function getName() : ?string;
/**
* Set the person's age
*
* @param int $age
*/
public function setAge(?int $age);
/**
* Get the person's age
*
* @return int
*/
public function getAge() : ?int;
}
Create a concrete implementation of your interface. Let it extend the default DataTransferObject
abstraction.
<?php
declare(strict_types=1);
use Aedart\DTO\DataTransferObject;
class Person extends DataTransferObject implements PersonInterface
{
protected $name = '';
protected $age = 0;
/**
* Set the person's name
*
* @param string $name
*/
public function setName(?string $name)
{
$this->name = $name;
}
/**
* Get the person's name
*
* @return string
*/
public function getName() : ?string
{
return $this->name;
}
/**
* Set the person's age
*
* @param int $age
*/
public function setAge(?int $age)
{
$this->age = $age;
}
/**
* Get the person's age
*
* @return int
*/
public function getAge() : ?int
{
return $this->age;
}
}
Now you are ready to use the DTO. The following sections will highlight some of the usage scenarios.
Each defined property is accessible in multiple ways, if a getter and or setter method has been defined for that given property.
For additional information, please read about Mutators and Accessor, PHP's overloading, and PHP's Array-Access
<?php
// Create a new instance of your DTO
$person = new Person();
// Name can be set using normal setter methods
$person->setName('John');
// But you can also just set the property itself
$person->name = 'Jack'; // Will automatically invoke setName()
// And you can also set it, using an array-accessor
$person['name'] = 'Jane'; // Will also automatically invoke setName()
// ... //
// Obtain age using the regular getter method
$age = $person->getAge();
// Can also get it via invoking the property directly
$age = $person->age; // Will automatically invoke getAge()
// Lastly, it can also be access via an array-accessor
$age = $person['age']; // Also invokes the getAge()
If you are using a modern IDE, then it will most likely support PHPDoc.
By adding a @property
tag to your interface or concrete implementation, your IDE will be able to auto-complete the overloadable properties.
You can populate your DTO using an array.
<?php
// property-name => value array
$data = [
'name' => 'Timmy Jones',
'age' => 32
];
// Create instance and invoke populate
$person = new Person();
$person->populate($data); // setName() and setAge() are invoked with the given values
If you are extending the default DTO abstraction, then you can also pass in an array in the constructor
<?php
// property-name => value array
$data = [
'name' => 'Carmen Rock',
'age' => 25
];
// Create instance and invoke populate
$person = new Person($data); // invokes populate(...), which then invokes the setter methods
Each DTO can be exported to an array.
<?php
// Provided that you have a populated instance, you can export those properties to an array
$properties = $person->toArray();
var_dump($properties); // Will output a "property-name => value" list
// Example:
// [
// 'name' => 'Timmy'
// 'age' => 16
// ]
All DTOs are Json serializable, meaning that they inherit from the JsonSerializable
interface.
This means that when using json_encode()
, the DTO automatically ensures that its properties are serializable by the encoding method.
<?php
$person = new Person([
'name' => 'Rian Dou',
'age' => 29
]);
echo json_encode($person);
The above example will output the following;
{
"name":"Rian Dou",
"age":29
}
You can also perform json serialization directly on the DTO, by invoking the toJson()
method.
<?php
$person = new Person([
'name' => 'Rian Dou',
'age' => 29
]);
echo $person->toJson(); // The same as invoking json_encode($person);
In this interpretation of the DTO design pattern, each instance must hold a reference to an IoC service container.
If you do not know what this means or how this works, please start off by reading the wiki-article about it.
If you are using this package inside a Laravel application, then you can skip this part; it is NOT needed!
<?php
use Aedart\DTO\Providers\Bootstrap;
// Invoke the bootstrap's boot method, before using any DTOs
// Ideally, this should happen along side your application other bootstrapping logic
Bootstrap::boot(); // A default service container is now available
Imagine that your Person
DTO accepts more complex properties, e.g. an address;
NOTE: This example will only work if;
a) You are using the DTO inside a Laravel application
or
b) You have invoked the Bootstrap::boot()
method, before using the given DTO (...once again this is not needed, if you are using this package inside a Laravel application)
<?php
declare(strict_types=1);
use Aedart\DTO\DataTransferObject;
// None-interfaced DTO class is on purpose for this example
class Address extends DataTransferObject
{
protected $street = '';
/**
* Set the street
*
* @param string $street
*/
public function setStreet(?string $street)
{
$this->street = $street;
}
/**
* Get the street
*
* @return string
*/
public function getStreet() : ?string
{
return $this->street;
}
}
// You Person DTO now accepts an address object
class Person extends DataTransferObject implements PersonInterface
{
protected $name = '';
protected $age = 0;
protected $address = null;
// ... getters and setters for name and age not shown ... //
/**
* Set the address
*
* @param Address $address
*/
public function setAddress(?Address $address)
{
$this->address = $address;
}
/**
* Get the address
*
* @return Address
*/
public function getAddress() : ?Address
{
return $this->address;
}
}
// ... some place else, in your application ... //
// Data for your Person DTO
$data = [
'name' => 'Arial Jackson',
'age' => 42,
// Notice that we are NOT passing in an instance of Address, but an array instead!
'address' => [
'street' => 'Somewhere str. 44'
]
];
$person = new Person($data);
$address = $person->getAddress(); // Instance of Address - Will automatically be resolved (if possible).
In the above example, Laravel's Service Container attempts to find and create any concrete instances that are expected.
Furthermore, the default DTO abstraction (Aedart\DTO\DataTransferObject
) will attempt to automatically populate that instance.
If you prefer to use interfaces instead, then you need to bind
those interfaces to concrete instances, before the DTOs / service container can handle and resolve them.
If you are outside a Laravel application, then you can bind interfaces to concrete instances, in the following way;
<?php
// Somewhere in your application's bootstrapping logic
use Aedart\DTO\Providers\Bootstrap;
// Boot up the service container
Bootstrap::boot();
// Register / bind your interfaces to concrete instances
Bootstrap::getContainer()->bind(CityInterface::class, function($app){
return new City();
});
Inside your application's service provider (or perhaps a custom service provider), you can bind your DTO interfaces to concrete instances;
<?php
// ... somewhere inside your service provider
// Register / bind your interfaces to concrete instances
$this->app->bind(CityInterface::class, function($app){
return new City();
});
Given that you have bound your interfaces to concrete instances, then the following is possible
<?php
use Aedart\DTO\Contracts\DataTransferObject as DataTransferObjectInterface;
use Aedart\DTO\DataTransferObject;
// Interface for a City
interface CityInterface extends DataTransferObjectInterface
{
/**
* Set the city's name
*
* @param string $name
*/
public function setName(string $name) : void;
/**
* Get the city's name
*
* @return string
*/
public function getName() : string;
}
// Concrete implementation of City
class City extends DataTransferObject implements CityInterface
{
protected $name = '';
// ... getter and setter implementation not shown ... //
}
// Address class now also accepts a city property, of the type CityInterface
class Address extends DataTransferObject
{
protected $street = '';
protected $city = null;
// ... street getter and setter implementation not shown ... //
/**
* Set the city
*
* @param CityInterface $address
*/
public function setCity(?CityInterface $city)
{
$this->city = $city;
}
/**
* Get the city
*
* @return CityInterface
*/
public function getCity() : ?CityInterface
{
return $this->city;
}
}
// ... some other place in your application ... //
$addressData = [
'street' => 'Marshall Street 27',
'city' => [
'name' => 'Lincoln'
]
];
// Create new instance and populate
$address = new Address($addressData); // Will attempt to automatically resolve the expected city property,
// of the CityInterface type, by creating a concrete City, using
// the service container, and resolve the bound interface instance
Have you found a defect ( bug or design flaw ), or do you wish improvements? In the following sections, you might find some useful information on how you can help this project. In any case, I thank you for taking the time to help me improve this project's deliverables and overall quality.
If you are convinced that you have found a bug, then at the very least you should create a new issue. In that given issue, you should as a minimum describe the following;
- Where is the defect located
- A good, short and precise description of the defect (Why is it a defect)
- How to replicate the defect
- (A possible solution for how to resolve the defect)
When time permits it, I will review your issue and take action upon it.
A good and well written bug report can help me a lot. Nevertheless, if you can or wish to resolve the defect by yourself, here is how you can do so;
- Fork this project
- Create a new local development branch for the given defect-fix
- Write your code / changes
- Create executable test-cases (prove that your changes are solid!)
- Commit and push your changes to your fork-repository
- Send a pull-request with your changes
- Drink a Beer - you earned it :)
As soon as I receive the pull-request (and have time for it), I will review your changes and merge them into this project. If not, I will inform you why I choose not to.
- Martin Fowler, for sharing his knowledge about DTOs and many other design patterns
- Taylor Otwell, for creating Laravel and especially the Service Container, that I'm using daily
- Jeffrey Way, for creating Laracasts - a great place to learn new things... And where I finally understood some of the principles of IoC!
This package follows Semantic Versioning 2.0.0
BSD-3-Clause, Read the LICENSE file included in this package