Code for a Person DTO, with tests
up vote
3
down vote
favorite
My needs are to manage DTO in a php5.3 application. I've written few lines of code in TDD with phpunit 6. The scenario I want to implement is a request with following fields:
- name
- surname
password
alias
email
address
- civic_number
- country
And I need to create DTO's with
- credentials
- info
- address
Provide DTO properties
Each DTO should provide its own properties.
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
'name',
'surname',
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
Getters
I also want that all properties are available via getters:
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->get('name')
);
}
Filtering
The main purpose here is to filter all fields and just keep consideration of the DTO's properties:
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'sfadfsa' => 'Simone',
));
$expectedProperties = array(
'name' => 'Simone',
'surname' => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
Serialization
Serializing and un serializing I want that my code works fine and without side effect.
public function testSerializationKeepSameProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'surname' => 'Gentili',
'sadfasdrname' => 'Gentili',
));
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
array(
'name' => 'Simone',
'surname' => 'Gentili',
),
$unserialized->asArray()
);
}
Are those tests good? I hope yes but any suggestions are welcome.
Finally, ... the code:
A DTO can be easily done with a simple class with public properties:
class Person extends Dto
{
public $name;
public $surname;
}
Here the production code:
abstract class Dto
{
private $properties;
private function __construct(array $properties)
{
$this->properties = $properties;
}
public static function createFromArray($properties)
{
return new static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return isset($this->properties[$propertyName])
? $this->properties[$propertyName]
: null;
}
public static function getPropertyNames()
{
$reflected = new ReflectionClass(new static(array()));
$properties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
return array_map(function ($item) {
return $item->getName();
}, $properties);
}
public function asArray()
{
$properties = array();
foreach (static::getPropertyNames() as $itemValue) {
$properties[$itemValue] = $this->get($itemValue);
}
return $properties;
}
public function __sleep()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->set(
$propertyName,
$this->get($propertyName)
);
}
return self::getPropertyNames();
}
public function __wakeup()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->properties[$propertyName] = $this->$propertyName;
$this->$propertyName = null;
}
return self::getPropertyNames();
}
}
php php5 dto
add a comment |
up vote
3
down vote
favorite
My needs are to manage DTO in a php5.3 application. I've written few lines of code in TDD with phpunit 6. The scenario I want to implement is a request with following fields:
- name
- surname
password
alias
email
address
- civic_number
- country
And I need to create DTO's with
- credentials
- info
- address
Provide DTO properties
Each DTO should provide its own properties.
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
'name',
'surname',
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
Getters
I also want that all properties are available via getters:
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->get('name')
);
}
Filtering
The main purpose here is to filter all fields and just keep consideration of the DTO's properties:
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'sfadfsa' => 'Simone',
));
$expectedProperties = array(
'name' => 'Simone',
'surname' => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
Serialization
Serializing and un serializing I want that my code works fine and without side effect.
public function testSerializationKeepSameProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'surname' => 'Gentili',
'sadfasdrname' => 'Gentili',
));
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
array(
'name' => 'Simone',
'surname' => 'Gentili',
),
$unserialized->asArray()
);
}
Are those tests good? I hope yes but any suggestions are welcome.
Finally, ... the code:
A DTO can be easily done with a simple class with public properties:
class Person extends Dto
{
public $name;
public $surname;
}
Here the production code:
abstract class Dto
{
private $properties;
private function __construct(array $properties)
{
$this->properties = $properties;
}
public static function createFromArray($properties)
{
return new static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return isset($this->properties[$propertyName])
? $this->properties[$propertyName]
: null;
}
public static function getPropertyNames()
{
$reflected = new ReflectionClass(new static(array()));
$properties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
return array_map(function ($item) {
return $item->getName();
}, $properties);
}
public function asArray()
{
$properties = array();
foreach (static::getPropertyNames() as $itemValue) {
$properties[$itemValue] = $this->get($itemValue);
}
return $properties;
}
public function __sleep()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->set(
$propertyName,
$this->get($propertyName)
);
}
return self::getPropertyNames();
}
public function __wakeup()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->properties[$propertyName] = $this->$propertyName;
$this->$propertyName = null;
}
return self::getPropertyNames();
}
}
php php5 dto
you do know php5.3 is kind of no longer supported? It stopped getting security updates in 2014! php.net/supported-versions.php
– Pinoniq
Jun 12 '17 at 13:11
Yes I know. ...
– sensorario
Jun 12 '17 at 13:24
It's no longer supported by the PHP team, but Red Hat should still be somewhat supporting 5.3 with some security updates as part of the RHEL 6 operating system for another three years.
– bdsl
Oct 21 '17 at 22:27
add a comment |
up vote
3
down vote
favorite
up vote
3
down vote
favorite
My needs are to manage DTO in a php5.3 application. I've written few lines of code in TDD with phpunit 6. The scenario I want to implement is a request with following fields:
- name
- surname
password
alias
email
address
- civic_number
- country
And I need to create DTO's with
- credentials
- info
- address
Provide DTO properties
Each DTO should provide its own properties.
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
'name',
'surname',
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
Getters
I also want that all properties are available via getters:
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->get('name')
);
}
Filtering
The main purpose here is to filter all fields and just keep consideration of the DTO's properties:
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'sfadfsa' => 'Simone',
));
$expectedProperties = array(
'name' => 'Simone',
'surname' => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
Serialization
Serializing and un serializing I want that my code works fine and without side effect.
public function testSerializationKeepSameProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'surname' => 'Gentili',
'sadfasdrname' => 'Gentili',
));
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
array(
'name' => 'Simone',
'surname' => 'Gentili',
),
$unserialized->asArray()
);
}
Are those tests good? I hope yes but any suggestions are welcome.
Finally, ... the code:
A DTO can be easily done with a simple class with public properties:
class Person extends Dto
{
public $name;
public $surname;
}
Here the production code:
abstract class Dto
{
private $properties;
private function __construct(array $properties)
{
$this->properties = $properties;
}
public static function createFromArray($properties)
{
return new static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return isset($this->properties[$propertyName])
? $this->properties[$propertyName]
: null;
}
public static function getPropertyNames()
{
$reflected = new ReflectionClass(new static(array()));
$properties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
return array_map(function ($item) {
return $item->getName();
}, $properties);
}
public function asArray()
{
$properties = array();
foreach (static::getPropertyNames() as $itemValue) {
$properties[$itemValue] = $this->get($itemValue);
}
return $properties;
}
public function __sleep()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->set(
$propertyName,
$this->get($propertyName)
);
}
return self::getPropertyNames();
}
public function __wakeup()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->properties[$propertyName] = $this->$propertyName;
$this->$propertyName = null;
}
return self::getPropertyNames();
}
}
php php5 dto
My needs are to manage DTO in a php5.3 application. I've written few lines of code in TDD with phpunit 6. The scenario I want to implement is a request with following fields:
- name
- surname
password
alias
email
address
- civic_number
- country
And I need to create DTO's with
- credentials
- info
- address
Provide DTO properties
Each DTO should provide its own properties.
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
'name',
'surname',
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
Getters
I also want that all properties are available via getters:
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->get('name')
);
}
Filtering
The main purpose here is to filter all fields and just keep consideration of the DTO's properties:
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'sfadfsa' => 'Simone',
));
$expectedProperties = array(
'name' => 'Simone',
'surname' => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
Serialization
Serializing and un serializing I want that my code works fine and without side effect.
public function testSerializationKeepSameProperties()
{
$dto = Person::createFromArray(array(
'name' => 'Simone',
'surname' => 'Gentili',
'sadfasdrname' => 'Gentili',
));
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
array(
'name' => 'Simone',
'surname' => 'Gentili',
),
$unserialized->asArray()
);
}
Are those tests good? I hope yes but any suggestions are welcome.
Finally, ... the code:
A DTO can be easily done with a simple class with public properties:
class Person extends Dto
{
public $name;
public $surname;
}
Here the production code:
abstract class Dto
{
private $properties;
private function __construct(array $properties)
{
$this->properties = $properties;
}
public static function createFromArray($properties)
{
return new static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return isset($this->properties[$propertyName])
? $this->properties[$propertyName]
: null;
}
public static function getPropertyNames()
{
$reflected = new ReflectionClass(new static(array()));
$properties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
return array_map(function ($item) {
return $item->getName();
}, $properties);
}
public function asArray()
{
$properties = array();
foreach (static::getPropertyNames() as $itemValue) {
$properties[$itemValue] = $this->get($itemValue);
}
return $properties;
}
public function __sleep()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->set(
$propertyName,
$this->get($propertyName)
);
}
return self::getPropertyNames();
}
public function __wakeup()
{
foreach (self::getPropertyNames() as $propertyName) {
$this->properties[$propertyName] = $this->$propertyName;
$this->$propertyName = null;
}
return self::getPropertyNames();
}
}
php php5 dto
php php5 dto
edited Jun 10 '17 at 7:04
200_success
127k15148412
127k15148412
asked Jun 10 '17 at 6:14
sensorario
1366
1366
you do know php5.3 is kind of no longer supported? It stopped getting security updates in 2014! php.net/supported-versions.php
– Pinoniq
Jun 12 '17 at 13:11
Yes I know. ...
– sensorario
Jun 12 '17 at 13:24
It's no longer supported by the PHP team, but Red Hat should still be somewhat supporting 5.3 with some security updates as part of the RHEL 6 operating system for another three years.
– bdsl
Oct 21 '17 at 22:27
add a comment |
you do know php5.3 is kind of no longer supported? It stopped getting security updates in 2014! php.net/supported-versions.php
– Pinoniq
Jun 12 '17 at 13:11
Yes I know. ...
– sensorario
Jun 12 '17 at 13:24
It's no longer supported by the PHP team, but Red Hat should still be somewhat supporting 5.3 with some security updates as part of the RHEL 6 operating system for another three years.
– bdsl
Oct 21 '17 at 22:27
you do know php5.3 is kind of no longer supported? It stopped getting security updates in 2014! php.net/supported-versions.php
– Pinoniq
Jun 12 '17 at 13:11
you do know php5.3 is kind of no longer supported? It stopped getting security updates in 2014! php.net/supported-versions.php
– Pinoniq
Jun 12 '17 at 13:11
Yes I know. ...
– sensorario
Jun 12 '17 at 13:24
Yes I know. ...
– sensorario
Jun 12 '17 at 13:24
It's no longer supported by the PHP team, but Red Hat should still be somewhat supporting 5.3 with some security updates as part of the RHEL 6 operating system for another three years.
– bdsl
Oct 21 '17 at 22:27
It's no longer supported by the PHP team, but Red Hat should still be somewhat supporting 5.3 with some security updates as part of the RHEL 6 operating system for another three years.
– bdsl
Oct 21 '17 at 22:27
add a comment |
1 Answer
1
active
oldest
votes
up vote
0
down vote
In the tests (and anywhere else), you should use constants to ensure correct spelling of the properties :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
...
$expectedProperties = array(self::PERSON_NAME,self::PERSON_SURNAME);
Then the Filtering and Serialization tests are very similar while they should be atomic (one test tests one thing) : you should not test Filtering whike testing Serialization.
In the production code there is one error : you set the desired property (i.e. $this->name
) while you get the internal array's key ($this->properties['name']
). To keep the simplicity of defining an object as a class with public properties you should drop the internal array. This will also make the __sleep
and __wakeup
calls much clearer.
Also a design flaw is to use the Reflection : it seems unavoidable but depending on your usage there might be performance issues. Using a caching system such as a static array will help. In particular if you look at the __wakeup
and __sleep
function you will notice a repeted call to the getPropertyNames
method.
Finally what might be problematic is that you don't check if the property exists when setting or getting. If it does not you should throw an exception. There are magic methods in PHP5.3 (__get
, __set
and __call
) ; you may do better using them. This way you will be able to use $personName = $person->getName()
/ $person->setName('Simone')
as well as $personName = $person->name
/ $person->name = 'Simone'
Tests :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
self::PERSON_NAME,
self::PERSON_SURNAME,
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->getName()
);
}
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
'non existent property' => 'Simone',
));
$expectedProperties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
public function testSerializationKeepSameProperties()
{
$properties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$dto = Person::createFromArray($properties);
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
$properties,
$unserialized->asArray()
);
}
Production code :
class DtoException extends Exception
{
}
abstract class Dto
{
private static $propertyNamesByClass = array();
private function __construct(array $properties)
{
foreach($properties as $propertyName=>$propertyValue)
if(Static::hasPropertyName($propertyName))
$this->set($propertyName,$propertyValue);
}
public function __call($method, array $arguments)
{
$getOrSet = substr($method, 0, 3);
if($getOrSet != 'get' && $getOrSet != 'set')
throw new DtoException('"'.get_class($this).'" has no method "'.$method.'"');
$propertyName = strtolower(substr($method, 3));
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
$getOrSetMethod = array($this,$getOrSet);
array_unshift($arguments,$propertyName);
return call_user_func_array($getOrSetMethod, $arguments);
}
public function __get($propertyName)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->get($propertyName);
}
public function __set($propertyName, $propertyValue)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->set($propertyName, $propertyValue);
}
public static function createFromArray($properties)
{
return new Static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return $this->$propertyName;
}
public static function getPropertyNames()
{
$className = get_called_class();
if(isset(self::$propertyNamesByClass[$className]))
return self::$propertyNamesByClass[$className];
$reflected = new ReflectionClass($className);
$reflectedProperties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
$propertyNames = array();
foreach($reflectedProperties as $reflectedProperty) {
$propertyNames = $reflectedProperty->getName();
}
self::$propertyNamesByClass[$className] = $propertyNames;
return $propertyNames;
}
public static function hasPropertyName($propertyName)
{
$propertyNames = Static::getPropertyNames();
return in_array($propertyName,$propertyNames);
}
public function asArray()
{
$values = array();
foreach (Static::getPropertyNames() as $propertyName) {
$values[$propertyName] = $this->get($propertyName);
}
return $values;
}
public function __sleep()
{
$propertyNames = self::getPropertyNames();
foreach ($propertyNames as $propertyName) {
$propertyValue = $this->get($propertyName);
$this->set($propertyName, $propertyValue);
}
return $propertyNames;
}
public function __wakeup()
{
$propertyNames = self::getPropertyNames();
return $propertyNames;
}
}
I tested it on PHP5.3 and it works, however I have to tell you that it does not work on PHP5.2- or PHP7+.
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
In the tests (and anywhere else), you should use constants to ensure correct spelling of the properties :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
...
$expectedProperties = array(self::PERSON_NAME,self::PERSON_SURNAME);
Then the Filtering and Serialization tests are very similar while they should be atomic (one test tests one thing) : you should not test Filtering whike testing Serialization.
In the production code there is one error : you set the desired property (i.e. $this->name
) while you get the internal array's key ($this->properties['name']
). To keep the simplicity of defining an object as a class with public properties you should drop the internal array. This will also make the __sleep
and __wakeup
calls much clearer.
Also a design flaw is to use the Reflection : it seems unavoidable but depending on your usage there might be performance issues. Using a caching system such as a static array will help. In particular if you look at the __wakeup
and __sleep
function you will notice a repeted call to the getPropertyNames
method.
Finally what might be problematic is that you don't check if the property exists when setting or getting. If it does not you should throw an exception. There are magic methods in PHP5.3 (__get
, __set
and __call
) ; you may do better using them. This way you will be able to use $personName = $person->getName()
/ $person->setName('Simone')
as well as $personName = $person->name
/ $person->name = 'Simone'
Tests :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
self::PERSON_NAME,
self::PERSON_SURNAME,
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->getName()
);
}
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
'non existent property' => 'Simone',
));
$expectedProperties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
public function testSerializationKeepSameProperties()
{
$properties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$dto = Person::createFromArray($properties);
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
$properties,
$unserialized->asArray()
);
}
Production code :
class DtoException extends Exception
{
}
abstract class Dto
{
private static $propertyNamesByClass = array();
private function __construct(array $properties)
{
foreach($properties as $propertyName=>$propertyValue)
if(Static::hasPropertyName($propertyName))
$this->set($propertyName,$propertyValue);
}
public function __call($method, array $arguments)
{
$getOrSet = substr($method, 0, 3);
if($getOrSet != 'get' && $getOrSet != 'set')
throw new DtoException('"'.get_class($this).'" has no method "'.$method.'"');
$propertyName = strtolower(substr($method, 3));
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
$getOrSetMethod = array($this,$getOrSet);
array_unshift($arguments,$propertyName);
return call_user_func_array($getOrSetMethod, $arguments);
}
public function __get($propertyName)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->get($propertyName);
}
public function __set($propertyName, $propertyValue)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->set($propertyName, $propertyValue);
}
public static function createFromArray($properties)
{
return new Static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return $this->$propertyName;
}
public static function getPropertyNames()
{
$className = get_called_class();
if(isset(self::$propertyNamesByClass[$className]))
return self::$propertyNamesByClass[$className];
$reflected = new ReflectionClass($className);
$reflectedProperties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
$propertyNames = array();
foreach($reflectedProperties as $reflectedProperty) {
$propertyNames = $reflectedProperty->getName();
}
self::$propertyNamesByClass[$className] = $propertyNames;
return $propertyNames;
}
public static function hasPropertyName($propertyName)
{
$propertyNames = Static::getPropertyNames();
return in_array($propertyName,$propertyNames);
}
public function asArray()
{
$values = array();
foreach (Static::getPropertyNames() as $propertyName) {
$values[$propertyName] = $this->get($propertyName);
}
return $values;
}
public function __sleep()
{
$propertyNames = self::getPropertyNames();
foreach ($propertyNames as $propertyName) {
$propertyValue = $this->get($propertyName);
$this->set($propertyName, $propertyValue);
}
return $propertyNames;
}
public function __wakeup()
{
$propertyNames = self::getPropertyNames();
return $propertyNames;
}
}
I tested it on PHP5.3 and it works, however I have to tell you that it does not work on PHP5.2- or PHP7+.
add a comment |
up vote
0
down vote
In the tests (and anywhere else), you should use constants to ensure correct spelling of the properties :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
...
$expectedProperties = array(self::PERSON_NAME,self::PERSON_SURNAME);
Then the Filtering and Serialization tests are very similar while they should be atomic (one test tests one thing) : you should not test Filtering whike testing Serialization.
In the production code there is one error : you set the desired property (i.e. $this->name
) while you get the internal array's key ($this->properties['name']
). To keep the simplicity of defining an object as a class with public properties you should drop the internal array. This will also make the __sleep
and __wakeup
calls much clearer.
Also a design flaw is to use the Reflection : it seems unavoidable but depending on your usage there might be performance issues. Using a caching system such as a static array will help. In particular if you look at the __wakeup
and __sleep
function you will notice a repeted call to the getPropertyNames
method.
Finally what might be problematic is that you don't check if the property exists when setting or getting. If it does not you should throw an exception. There are magic methods in PHP5.3 (__get
, __set
and __call
) ; you may do better using them. This way you will be able to use $personName = $person->getName()
/ $person->setName('Simone')
as well as $personName = $person->name
/ $person->name = 'Simone'
Tests :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
self::PERSON_NAME,
self::PERSON_SURNAME,
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->getName()
);
}
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
'non existent property' => 'Simone',
));
$expectedProperties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
public function testSerializationKeepSameProperties()
{
$properties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$dto = Person::createFromArray($properties);
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
$properties,
$unserialized->asArray()
);
}
Production code :
class DtoException extends Exception
{
}
abstract class Dto
{
private static $propertyNamesByClass = array();
private function __construct(array $properties)
{
foreach($properties as $propertyName=>$propertyValue)
if(Static::hasPropertyName($propertyName))
$this->set($propertyName,$propertyValue);
}
public function __call($method, array $arguments)
{
$getOrSet = substr($method, 0, 3);
if($getOrSet != 'get' && $getOrSet != 'set')
throw new DtoException('"'.get_class($this).'" has no method "'.$method.'"');
$propertyName = strtolower(substr($method, 3));
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
$getOrSetMethod = array($this,$getOrSet);
array_unshift($arguments,$propertyName);
return call_user_func_array($getOrSetMethod, $arguments);
}
public function __get($propertyName)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->get($propertyName);
}
public function __set($propertyName, $propertyValue)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->set($propertyName, $propertyValue);
}
public static function createFromArray($properties)
{
return new Static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return $this->$propertyName;
}
public static function getPropertyNames()
{
$className = get_called_class();
if(isset(self::$propertyNamesByClass[$className]))
return self::$propertyNamesByClass[$className];
$reflected = new ReflectionClass($className);
$reflectedProperties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
$propertyNames = array();
foreach($reflectedProperties as $reflectedProperty) {
$propertyNames = $reflectedProperty->getName();
}
self::$propertyNamesByClass[$className] = $propertyNames;
return $propertyNames;
}
public static function hasPropertyName($propertyName)
{
$propertyNames = Static::getPropertyNames();
return in_array($propertyName,$propertyNames);
}
public function asArray()
{
$values = array();
foreach (Static::getPropertyNames() as $propertyName) {
$values[$propertyName] = $this->get($propertyName);
}
return $values;
}
public function __sleep()
{
$propertyNames = self::getPropertyNames();
foreach ($propertyNames as $propertyName) {
$propertyValue = $this->get($propertyName);
$this->set($propertyName, $propertyValue);
}
return $propertyNames;
}
public function __wakeup()
{
$propertyNames = self::getPropertyNames();
return $propertyNames;
}
}
I tested it on PHP5.3 and it works, however I have to tell you that it does not work on PHP5.2- or PHP7+.
add a comment |
up vote
0
down vote
up vote
0
down vote
In the tests (and anywhere else), you should use constants to ensure correct spelling of the properties :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
...
$expectedProperties = array(self::PERSON_NAME,self::PERSON_SURNAME);
Then the Filtering and Serialization tests are very similar while they should be atomic (one test tests one thing) : you should not test Filtering whike testing Serialization.
In the production code there is one error : you set the desired property (i.e. $this->name
) while you get the internal array's key ($this->properties['name']
). To keep the simplicity of defining an object as a class with public properties you should drop the internal array. This will also make the __sleep
and __wakeup
calls much clearer.
Also a design flaw is to use the Reflection : it seems unavoidable but depending on your usage there might be performance issues. Using a caching system such as a static array will help. In particular if you look at the __wakeup
and __sleep
function you will notice a repeted call to the getPropertyNames
method.
Finally what might be problematic is that you don't check if the property exists when setting or getting. If it does not you should throw an exception. There are magic methods in PHP5.3 (__get
, __set
and __call
) ; you may do better using them. This way you will be able to use $personName = $person->getName()
/ $person->setName('Simone')
as well as $personName = $person->name
/ $person->name = 'Simone'
Tests :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
self::PERSON_NAME,
self::PERSON_SURNAME,
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->getName()
);
}
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
'non existent property' => 'Simone',
));
$expectedProperties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
public function testSerializationKeepSameProperties()
{
$properties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$dto = Person::createFromArray($properties);
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
$properties,
$unserialized->asArray()
);
}
Production code :
class DtoException extends Exception
{
}
abstract class Dto
{
private static $propertyNamesByClass = array();
private function __construct(array $properties)
{
foreach($properties as $propertyName=>$propertyValue)
if(Static::hasPropertyName($propertyName))
$this->set($propertyName,$propertyValue);
}
public function __call($method, array $arguments)
{
$getOrSet = substr($method, 0, 3);
if($getOrSet != 'get' && $getOrSet != 'set')
throw new DtoException('"'.get_class($this).'" has no method "'.$method.'"');
$propertyName = strtolower(substr($method, 3));
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
$getOrSetMethod = array($this,$getOrSet);
array_unshift($arguments,$propertyName);
return call_user_func_array($getOrSetMethod, $arguments);
}
public function __get($propertyName)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->get($propertyName);
}
public function __set($propertyName, $propertyValue)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->set($propertyName, $propertyValue);
}
public static function createFromArray($properties)
{
return new Static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return $this->$propertyName;
}
public static function getPropertyNames()
{
$className = get_called_class();
if(isset(self::$propertyNamesByClass[$className]))
return self::$propertyNamesByClass[$className];
$reflected = new ReflectionClass($className);
$reflectedProperties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
$propertyNames = array();
foreach($reflectedProperties as $reflectedProperty) {
$propertyNames = $reflectedProperty->getName();
}
self::$propertyNamesByClass[$className] = $propertyNames;
return $propertyNames;
}
public static function hasPropertyName($propertyName)
{
$propertyNames = Static::getPropertyNames();
return in_array($propertyName,$propertyNames);
}
public function asArray()
{
$values = array();
foreach (Static::getPropertyNames() as $propertyName) {
$values[$propertyName] = $this->get($propertyName);
}
return $values;
}
public function __sleep()
{
$propertyNames = self::getPropertyNames();
foreach ($propertyNames as $propertyName) {
$propertyValue = $this->get($propertyName);
$this->set($propertyName, $propertyValue);
}
return $propertyNames;
}
public function __wakeup()
{
$propertyNames = self::getPropertyNames();
return $propertyNames;
}
}
I tested it on PHP5.3 and it works, however I have to tell you that it does not work on PHP5.2- or PHP7+.
In the tests (and anywhere else), you should use constants to ensure correct spelling of the properties :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
...
$expectedProperties = array(self::PERSON_NAME,self::PERSON_SURNAME);
Then the Filtering and Serialization tests are very similar while they should be atomic (one test tests one thing) : you should not test Filtering whike testing Serialization.
In the production code there is one error : you set the desired property (i.e. $this->name
) while you get the internal array's key ($this->properties['name']
). To keep the simplicity of defining an object as a class with public properties you should drop the internal array. This will also make the __sleep
and __wakeup
calls much clearer.
Also a design flaw is to use the Reflection : it seems unavoidable but depending on your usage there might be performance issues. Using a caching system such as a static array will help. In particular if you look at the __wakeup
and __sleep
function you will notice a repeted call to the getPropertyNames
method.
Finally what might be problematic is that you don't check if the property exists when setting or getting. If it does not you should throw an exception. There are magic methods in PHP5.3 (__get
, __set
and __call
) ; you may do better using them. This way you will be able to use $personName = $person->getName()
/ $person->setName('Simone')
as well as $personName = $person->name
/ $person->name = 'Simone'
Tests :
const PERSON_NAME = 'name';
const PERSON_SURNAME = 'surname';
public function testProvideDtoPropertyNames()
{
$propertyNames = Person::getPropertyNames();
$expectedProperties = array(
self::PERSON_NAME,
self::PERSON_SURNAME,
);
$this->assertEquals(
$expectedProperties,
$propertyNames
);
}
public function testProvidePropertyViaGeneralGetter()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
));
$this->assertEquals(
'Simone',
$dto->getName()
);
}
public function testDTOAcceptOnlyItsOwnProperties()
{
$dto = Person::createFromArray(array(
self::PERSON_NAME => 'Simone',
'non existent property' => 'Simone',
));
$expectedProperties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$this->assertEquals(
$expectedProperties,
$dto->asArray()
);
}
public function testSerializationKeepSameProperties()
{
$properties = array(
self::PERSON_NAME => 'Simone',
self::PERSON_SURNAME => null,
);
$dto = Person::createFromArray($properties);
$serialized = serialize($dto);
$unserialized = unserialize($serialized);
$this->assertEquals(
$dto->asArray(),
$unserialized->asArray()
);
$this->assertEquals(
$properties,
$unserialized->asArray()
);
}
Production code :
class DtoException extends Exception
{
}
abstract class Dto
{
private static $propertyNamesByClass = array();
private function __construct(array $properties)
{
foreach($properties as $propertyName=>$propertyValue)
if(Static::hasPropertyName($propertyName))
$this->set($propertyName,$propertyValue);
}
public function __call($method, array $arguments)
{
$getOrSet = substr($method, 0, 3);
if($getOrSet != 'get' && $getOrSet != 'set')
throw new DtoException('"'.get_class($this).'" has no method "'.$method.'"');
$propertyName = strtolower(substr($method, 3));
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
$getOrSetMethod = array($this,$getOrSet);
array_unshift($arguments,$propertyName);
return call_user_func_array($getOrSetMethod, $arguments);
}
public function __get($propertyName)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->get($propertyName);
}
public function __set($propertyName, $propertyValue)
{
if(!Static::hasPropertyName($propertyName))
throw new DtoException('"'.get_class($this).'" has no property "'.$propertyName.'"');
return $this->set($propertyName, $propertyValue);
}
public static function createFromArray($properties)
{
return new Static($properties);
}
public function set($propertyName, $propertyValue)
{
$this->$propertyName = $propertyValue;
}
public function get($propertyName)
{
return $this->$propertyName;
}
public static function getPropertyNames()
{
$className = get_called_class();
if(isset(self::$propertyNamesByClass[$className]))
return self::$propertyNamesByClass[$className];
$reflected = new ReflectionClass($className);
$reflectedProperties = $reflected->getProperties(
ReflectionProperty::IS_PUBLIC
);
$propertyNames = array();
foreach($reflectedProperties as $reflectedProperty) {
$propertyNames = $reflectedProperty->getName();
}
self::$propertyNamesByClass[$className] = $propertyNames;
return $propertyNames;
}
public static function hasPropertyName($propertyName)
{
$propertyNames = Static::getPropertyNames();
return in_array($propertyName,$propertyNames);
}
public function asArray()
{
$values = array();
foreach (Static::getPropertyNames() as $propertyName) {
$values[$propertyName] = $this->get($propertyName);
}
return $values;
}
public function __sleep()
{
$propertyNames = self::getPropertyNames();
foreach ($propertyNames as $propertyName) {
$propertyValue = $this->get($propertyName);
$this->set($propertyName, $propertyValue);
}
return $propertyNames;
}
public function __wakeup()
{
$propertyNames = self::getPropertyNames();
return $propertyNames;
}
}
I tested it on PHP5.3 and it works, however I have to tell you that it does not work on PHP5.2- or PHP7+.
edited Jan 24 at 23:05
answered Jan 24 at 22:00
Geompse
912
912
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f165417%2fcode-for-a-person-dto-with-tests%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
you do know php5.3 is kind of no longer supported? It stopped getting security updates in 2014! php.net/supported-versions.php
– Pinoniq
Jun 12 '17 at 13:11
Yes I know. ...
– sensorario
Jun 12 '17 at 13:24
It's no longer supported by the PHP team, but Red Hat should still be somewhat supporting 5.3 with some security updates as part of the RHEL 6 operating system for another three years.
– bdsl
Oct 21 '17 at 22:27