aedart / dto
A variation / interpretation of the Data Transfer Object (DTO) design pattern (Distribution Pattern). Provides an abstraction for such DTOs
Installs: 10 802
Dependents: 1
Suggesters: 1
Security: 0
Stars: 27
Watchers: 5
Forks: 3
Open Issues: 0
Requires
- php: >=7.1.0
- aedart/overload: ~5.0
- aedart/util: ~5.0
- illuminate/container: 5.6.*
- illuminate/support: 5.6.*
Requires (Dev)
- aedart/license: 1.*
- aedart/license-file-manager: ~2.0
- aedart/testing: ~2.0
- symfony/var-dumper: ~4.0
README
Deprecated - Data Transfer Object (DTO)
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.
Contents
- When to use this
- How to install
- Quick start
- Advanced usage
- Contribution
- Acknowledgement
- Versioning
- License
When to use this
- 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.
How to install
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.
Quick start
Custom Interface for your DTO
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; }
Concrete implementation of your DTO
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.
Property overloading
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()
Tip: PHPDoc's property-tag
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.
Populating via an array
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
Export properties to array
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 // ]
Serialize to Json
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);
Advanced usage
Inversion of Control (IoC) / Dependency Injection
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.
Bootstrapping a service container
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
Nested instances
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.
Interface bindings
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.
Outside Laravel Application
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 Laravel Application
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(); });
Example
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
Contribution
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.
Bug Report
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.
Fork, code and send pull-request
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.
Acknowledgement
- 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!
Versioning
This package follows Semantic Versioning 2.0.0
License
BSD-3-Clause, Read the LICENSE file included in this package