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();
}
}









share|improve this question
























  • 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















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();
}
}









share|improve this question
























  • 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













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();
}
}









share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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


















  • 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










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+.






share|improve this answer























    Your Answer





    StackExchange.ifUsing("editor", function () {
    return StackExchange.using("mathjaxEditing", function () {
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    });
    });
    }, "mathjax-editing");

    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "196"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    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

























    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+.






    share|improve this answer



























      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+.






      share|improve this answer

























        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+.






        share|improve this answer














        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+.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Jan 24 at 23:05

























        answered Jan 24 at 22:00









        Geompse

        912




        912






























            draft saved

            draft discarded




















































            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.




            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            Сан-Квентин

            Алькесар

            Josef Freinademetz