PHP Reflection -- is this a bug or expected behavior? - php

I am creating a class that uses ReflectionProperty and am getting strange results.
Essentially, calling $reflectionProperty->getType()->getName() is returning the value of some entirely unrelated array. It does this under seemingly random conditions.
See below:
// this will be used as a type hinted property.
class DummyClass {
public function __construct($arr) {}
}
// a class that sets its property values reflectively
class BaseClass {
/** #var ReflectionProperty[] */
private static $publicProps = [];
/**
* Gets public ReflectionProperties of the concrete class, and caches them
* so we do not need to perform reflection again for this concrete class.
*
* #return ReflectionProperty[]
* #throws ReflectionException
*/
private function getPublicProps(){
if (!static::$publicProps) {
$concreteClass = get_class($this);
static::$publicProps = (new ReflectionClass($concreteClass))
->getProperties(ReflectionProperty::IS_PUBLIC);
}
return static::$publicProps;
}
/**
* For each public property in this class set value to the corresponding value from $propArr.
*
* #param $propArr
* #throws ReflectionException
*/
public function __construct($propArr) {
$concreteClass = get_class($this);
echo "Creating new instance of $concreteClass<br>";
foreach ($this->getPublicProps() as $prop) {
// get which property to set, its class, and value to pass to constructor
$propName = $prop->getName();
$propClass = $prop->getType()->getName();
$propValue = $propArr[$propName];
$propValueStr = var_export($propValue, true);
// print out what we are about to do, and assert $propClass is correct.
echo "---Setting: ->$propName = new $propClass($propValueStr)<br>";
assert($propClass === "DummyClass", "$propClass !== DummyClass");
// create the instance and assign it
$refClass = new ReflectionClass($propClass);
$this->$propName = $refClass->newInstanceArgs([$propValue]);
}
}
}
// a concrete implementation of the above class, with only 1 type hinted property.
class ConcreteClass extends BaseClass {
public DummyClass $prop1;
}
// should create an instance of ConcreteClass
// with ->prop1 = new DummyClass(["foo"=>"abc123"])
$testArr1 = [
"prop1" => ["foo" => "abc123"]
];
// should create an instance of ConcreteClass
// with ->prop1 = new DummyClass(["boo"=>"abc123def456"])
$testArr2 = [
"prop1" => ["boo" => "abc123def456"]
];
$tc1 = new ConcreteClass($testArr1);
echo "Created TestClass1...<br><br>";
$tc2 = new ConcreteClass($testArr2);
echo "Created TestClass2...<br><br>";
die;
The results:
Creating new instance of ConcreteClass
Setting: ->prop1 = new DummyClass(array ( 'foo' => 'abc123', ))
Created TestClass1...
Creating new instance of ConcreteClass
Setting: ->prop1 = new abc123def456(array ( 'boo' => 'abc123def456', ))
Error: assert(): abc123def456 !== DummyClass failed
Notice that the value of $propClass is abc123def456 -- how did that happen?
More Weirdness
Change the value of "abc123def456" to "12345678" and it will work.
Change the value of "abc123def456" to "123456789" and it will not work.
Omit the var_export(), and it will work. (Though, it may still break in other cases).
My gut tells me this is a PHP bug, but I might be doing something wrong, and/or this may be documented somewhere. I would like some clarification, because as of right now my only reliable solution is to not cache the reflected $publicProps. This results in an unnecessary call to ReflectionClass->getProperties() every single time I create a new ConcreteClass, which I'd like to avoid.

Turns out this was a bug in PHP: https://bugs.php.net/bug.php?id=79820
Minimal reproduction here: https://3v4l.org/kchfm
Fixed in 7.4.9: https://www.php.net/ChangeLog-7.php#7.4.9

Related

Options for "AutoMapping" from a DTO / Domain Entity with protected properties to "View Model" with public properties in PHP 5.x

Looking for "AutoMapper"-like feature/framework/pattern for taking data from a Doctrine 2 domain entity / DTO and mapping the protected properties on that entity to matching public properties on a View Model.
$userEntity = $this-em->find(User::class, 1);
$userViewModel = AutoMapper->Map($userEntity, new UserViewModel());
Where the only significant difference between User and UserViewModel is that User contains get/set accessors with protected backing fields (per doctrine's instructions), whereas UserViewModel contains public properties that match in name [a subset of] the protected backing fields on User.
An thoughts on how to accomplish this? (preferably without reflection)
Note that the domain entity has public get accessor, so the solution can leverage those accessors.
Came up with my own crude, yet effective, bare-bones implementation of AutoMapper for PHP to solve this problem for me. This method will map from public properties or public getters (convention based naming) to public properties on the target entity.
Hope this helps someone out:
class Mapper
{
/**
* This method will attempt to source all public property values on $target from $source.
*
* By convention, it'll look for properties on source with the same name,
* .. and will fallback camel-cased get/set accessors to use.
*
* Note that underscores in properties will be translated to capital letters in camel-cased getters.
*
* #param $source object
* #param $target object
* #return object
* #throws Exception
*/
public static function Map($source, $target)
{
$targetProperties = get_object_vars($target);
$sourceProperties = get_object_vars($source);
foreach ($targetProperties as $name => $value)
{
//
// match properties
//
$matchingSourcePropertyExists = array_key_exists($name, $sourceProperties);
if ($matchingSourcePropertyExists)
{
$target->{$name} = $source->{$name};
continue;
}
//
// fall back on matching by convention-based get accessors
//
$sourceMethods = get_class_methods(get_class($source));
$getterName = "get" . self::convertToPascalCase($name);
$matchingGetAccessorExists = in_array($getterName, $sourceMethods);
if ($matchingGetAccessorExists)
{
$target->{$name} = $source->{$getterName}();
continue;
}
//
// if we ever fail to map an entity on the target, throw
//
$className = get_class($target);
throw new Exception("Could not auto-map property $name on $className.");
}
return $target;
}
/**
* Converts this_kind_of_string into ThisKindOfString.
* #param $value string
* #return string
*/
private static function convertToPascalCase($value)
{
$value[0] = strtoupper($value[0]);
$func = create_function('$c', 'return strtoupper($c[1]);');
return preg_replace_callback('/_([a-z])/', $func, $value);
}
}
My implementation of automapping using Symfony's components
<?php
declare(strict_types=1);
namespace App\ApiResource\Utils;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
class GenericMapper
{
public static function map(object $source, object $target): void
{
$reflectionExtractor = new ReflectionExtractor();
$sourceProperties = $reflectionExtractor->getProperties($source::class);
$propertyAccessor = PropertyAccess::createPropertyAccessor();
foreach ($sourceProperties as $propertyName) {
if ($propertyAccessor->isWritable($target, $propertyName) && $propertyAccessor->isReadable($source, $propertyName)) {
$propertyAccessor->setValue($target, $propertyName, $propertyAccessor->getValue($source, $propertyName));
}
}
}
}

private property access with setter from constructor vs direct access from constructor

In TrainBoardingCard we have setTrainCode is it ok to use $this->trainCode = $trainCode inside constructor or we shoud always use setTrainCode like $this->setTrainCode($trainCode); as it could have some logic in future.
For both case what are the pros and cons? please let us know your preference with reasons.
class TrainBoardingCard extends BoardingCard
{
/**
* #var string
*/
private $trainCode;
/**
* #param string $trainCode
*/
public function __construct(string $trainCode)
{
$this->trainCode = $trainCode;
}
/**
* #return string
*/
public function getTrainCode(): string
{
return $this->trainCode;
}
/**
* #param string $trainCode
*/
public function setTrainCode(string $trainCode)
{
$this->trainCode = $trainCode;
}
}
It depends.
You could say there are two different schools of thought and they both deal with both setters and constructors.
The object must be created already valid state. This state can be changed with atomic operation from one valid state to another. This means, that you class doesn't actually have simple setters per se.
$client = new Client(
new FullName($name, $surname),
new Country($country);
new Address($city, street, $number));
// do something
$client->changeLocation(
new Country('UK'),
new Address('London', 'Downing St.', '10'));
Constructor is used only do pass in dependencies and not to impart state. The object's state by default is blank and it gets changed only by using setters externally.
$client new Client();
$client->setId(14);
$mapper = new DataMapper(new PDO('...'), $config);
$mapper->fetch($client);
if ($client->getCity() === 'Berlin') {
$client->setCity('London');
$mapper->store($client);
}
Or you can have a mix or both, but that would cause some confusion.
Not sure if this will make it better for you or worse :)

Nice Syntaxe for creating new object from method

There is a shortcut method to create an object from a method that return a string?
For the moment, I used that :
class MyClass {
/**
* #return string
*/
public function getEntityName() {
return 'myEntityName';
}
}
$myClassInstance = new MyClass();
// Need to get string
$entityName = $myclassInstance->getEntityName();
// And after I can instantiate it
$entity = new $entityName();
There is short-cut syntax for getting the string but not for creating the object from the string to date in PHP. See the following code in which I also include a 'myEntityName' class:
<?php
class myEntityName {
public function __construct(){
echo "Greetings from " . __CLASS__,"\n";
}
}
class MyClass {
/**
* #return string
*/
public function getEntityName() {
return 'myEntityName';
}
}
$entityName = ( new MyClass() )->getEntityName();
$entity = new $entityName();
With one line the code instantiates a MyClass object and executes its getEntityName method which returns the string $entityName. Interestingly, if I replace my one-liner with the following it fails in all versions of PHP except for the HipHop Virtual Machine (hhvm-3.0.1 - 3.4.0):
$entityName = new ( ( new MyClass() )->getEntityName() );

zend framework 2 doctrine hydration strategy - hydrate method never called

I am currently working on a project involving Zend Framework 2 and Doctrine.
I am implementing a Zend\Form and using the DoctrineORMModule\Stdlib\Hydrator\DoctrineEntity to extract and hydrate data from/to the database.
In much of the tutorials I have read, it is ideal to implement the Zend\Stdlib\Hydrator\Strategy\StrategyInterface when you need to convert a specific value before hydrating an object (in my example a date time value string which is an issue when using Doctrine). However, despite my best efforts to implement this, it seems only that the extract() method in my hydration strategy is called, never the hydrate() method. EVER!
To provide a code example, this is what I am doing - I have shortened some aspects of the code for brevity;
// Service
public function getProposerForm() {
// get required classes from service manager
$proposerEntity = $this->getServiceManager()->get('tourers_entity_proposer');
$entityManager = $this->getServiceManager()->get('Doctrine\ORM\EntityManager');
$formManager = $this->getServiceManager()->get('FormElementManager');
$proposerFieldset = $formManager->get('tourers_form_proposer_fieldset');
$proposerForm = $formManager->get('tourers_form_proposal');
$proposerFieldset->setUseAsBaseFieldset(true);
$proposerForm->add($proposerFieldset);
$proposerForm->get('submit')->setValue('Continue');
$proposerForm->bind($proposerEntity);
return $proposerForm;
}
.
// Controller
public function proposerAction() {
// grab the form from the form service
$formService = $this->getServiceLocator()->get('tourers_service_forms');
$form = $formService->getProposerForm();
if (true === $this->getRequest()->isPost()) {
$form->setData($this->getRequest()->getPost());
if (true === $form->isValid()) {
$proposerEntity = $form->getData();
$encryptedPolicyId = $formService->saveProposerForm($proposerEntity, $policyId);
return $this->redirect()->toRoute('tourers/proposal/caravan',array('policyid' => $encryptedPolicyId));
} else {
$errors = $form->getMessages();
var_dump($errors);
}
}
// view
return new ViewModel(array(
'form' => $form
,'policyid' => $policyId
)
);
}
.
// Form
class ProposerFieldset extends Fieldset implements InputFilterProviderInterface, ObjectManagerAwareInterface
{
/**
* #var Doctrine\ORM\EntityManager
*/
private $objectManager;
/**
* #return Zend\Form\Fieldset
*/
public function init()
{
// set name
parent::__construct('Proposer');
// set the hydrator to the domain object
$hydrator = new DoctrineEntity($this->objectManager,true);
$hydrator->addStrategy('proposerDateOfBirth',new DateStrategy);
$this->setHydrator($hydrator);
// other form elements below here including proposerDateOfBirth
$minDate = date('dd\/mm\/yyyy',strtotime('-100 years'));
$maxDate = date('dd\/mm\/yyyy',strtotime('-16 years'));
$this->add(array(
'name' => 'proposerDateOfBirth'
,'type' => 'Zend\Form\Element\Date'
,'attributes' => array(
'class' => 'form-control'
,'id' => 'proposerDateOfBirth'
,'placeholder' => 'dd/mm/yyyy'
,'min' => $minDate
,'max' => $maxDate
,'data-date-format' => 'dd/mm/yyyy'
),
'options' => array(
'label' => 'Date of Birth',
)
));
}
}
.
// Hydrator Strategy
namespace Tourers\Hydrator\Strategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class DateStrategy implements StrategyInterface {
/**
* (non-PHPdoc)
* #see Zend\Stdlib\Hydrator\Strategy.StrategyInterface::extract()
*/
public function extract($value) {
var_dump($value . ' extracted'); // GETS CALLED
return $value;
}
/**
* (non-PHPdoc)
* #see Zend\Stdlib\Hydrator\Strategy.StrategyInterface::hydrate()
*/
public function hydrate($value) {
var_dump($value . ' hydrated'); // NEVER CALLED
return $value;
}
}
I also discovered this behaviour. As yet I am still lost as to why my custom strategy was not being called.
The Strategies are applied inside the hydrateValue() and extractValue() functions, so its necessary for these functions to be called by the hydrate() and extract() functions in order for Strategies or Custom Strategies to be applied.
My problem was evident in the hydrateByReference() function inside DoctrineModule\Stdlib\Hydrator\DoctrineObject.
It seems that $this->hydrateValue(...) is only called the field has "associations". I do not yet know what these "associations" are, or what I am doing wrong for them to not exist in my data.
When I compared it to the extractByReference() function, I noticed that it always calls $this->extractValue() and does not require any "associations".
In my application, I had already implemented a custom Hydrator class. This means that when I create a form, the Hydrator and Strategies are automatically applied.
Inside my Form's init() function, I assign the custom Hydrator.
Inside my Custom Hydrator's __construct(), I add the strategies, and Custom Strategies.
So all I needed to do was to override hydrateByReference(...) in my Custom Hydrator to solve the problem. The example is below.
NOTES:
You may also need to override hydrateByValue(...) if you use "by value" hydration.
My solution may break the functionality of "associations".
My Custom Hydrator class:
class maintenance
extends DoctrineHydrator
{
/**
* Constructor
*
* #param ObjectManager $objectManager The ObjectManager to use
*/
public function __construct($objectManager)
{
/*
* Just call the parent class.
*/
parent::__construct($objectManager,
'Maintenance\Entity\maintenance', // The FQCN of the hydrated/extracted object
false // If set to true, hydrator will always use entity's public API
);
/*
* Now set up our strategies, and attach them
* to the appropriate fields.
*/
$this->addStrategy('purchasedate', new TIGDateStrategy());
$this->addStrategy('maintexpirydate', new TIGDateStrategy());
}
/**
* SF Modification, to ensure we call this->hydrateValue on
* all values before doing anything else with the data.
* This way, we convert the data first, before trying to
* store it in a DoctrineEntity.
*
* Hydrate the object using a by-reference logic (this means that values are modified directly without
* using the public API, in this case setters, and hence override any logic that could be done in those
* setters)
*
* #param array $data
* #param object $object
* #return object
*/
protected function hydrateByReference(array $data, $object)
{
$object = $this->tryConvertArrayToObject($data, $object);
$metadata = $this->metadata;
$refl = $metadata->getReflectionClass();
foreach ($data as $field => $value) {
// Ignore unknown fields
if (!$refl->hasProperty($field)) {
continue;
}
// SF Mod
$value = $this->hydrateValue($field, $value);
// End SF Mod
$value = $this->handleTypeConversions($value, $metadata->getTypeOfField($field));
$reflProperty = $refl->getProperty($field);
$reflProperty->setAccessible(true);
if ($metadata->hasAssociation($field)) {
$target = $metadata->getAssociationTargetClass($field);
if ($metadata->isSingleValuedAssociation($field)) {
$value = $this->toOne($target, $this->hydrateValue($field, $value));
$reflProperty->setValue($object, $value);
} elseif ($metadata->isCollectionValuedAssociation($field)) {
$this->toMany($object, $field, $target, $value);
}
} else {
$reflProperty->setValue($object, $value);
}
}
return $object;
}
}

phpunit - mockbuilder - set mock object internal property

Is it possible to create a mock object with disabled constructor and manually setted protected properties?
Here is an idiotic example:
class A {
protected $p;
public function __construct(){
$this->p = 1;
}
public function blah(){
if ($this->p == 2)
throw Exception();
}
}
class ATest extend bla_TestCase {
/**
#expectedException Exception
*/
public function testBlahShouldThrowExceptionBy2PValue(){
$mockA = $this->getMockBuilder('A')
->disableOriginalConstructor()
->getMock();
$mockA->p=2; //this won't work because p is protected, how to inject the p value?
$mockA->blah();
}
}
So I wanna inject the p value which is protected, so I can't. Should I define setter or IoC, or I can do this with phpunit?
You can make the property public by using Reflection, and then set the desired value:
$a = new A;
$reflection = new ReflectionClass($a);
$reflection_property = $reflection->getProperty('p');
$reflection_property->setAccessible(true);
$reflection_property->setValue($a, 2);
Anyway in your example you don't need to set p value for the Exception to be raised. You are using a mock for being able to take control over the object behaviour, without taking into account it's internals.
So, instead of setting p = 2 so an Exception is raised, you configure the mock to raise an Exception when the blah method is called:
$mockA = $this->getMockBuilder('A')
->disableOriginalConstructor()
->getMock();
$mockA->expects($this->any())
->method('blah')
->will($this->throwException(new Exception));
Last, it's strange that you're mocking the A class in the ATest. You usually mock the dependencies needed by the object you're testing.
Hope this helps.
Thought i'd leave a handy helper method that could be quickly copy and pasted here:
/**
* Sets a protected property on a given object via reflection
*
* #param $object - instance in which protected value is being modified
* #param $property - property on instance being modified
* #param $value - new value of the property being modified
*
* #return void
*/
public function setProtectedProperty($object, $property, $value)
{
$reflection = new ReflectionClass($object);
$reflection_property = $reflection->getProperty($property);
$reflection_property->setAccessible(true);
$reflection_property->setValue($object, $value);
}
Based on the accepted answer from #gontrollez, since we are using a mock builder we do not have the need to call new A; since we can use the class name instead.
$a = $this->getMockBuilder(A::class)
->disableOriginalConstructor()
->getMock();
$reflection = new ReflectionClass(A::class);
$reflection_property = $reflection->getProperty('p');
$reflection_property->setAccessible(true);
$reflection_property->setValue($a, 2);
Based on #rsahai91 answer above, created a new helper for making multiple methods accessible. Can be private or protected
/**
* Makes any properties (private/protected etc) accessible on a given object via reflection
*
* #param $object - instance in which properties are being modified
* #param array $properties - associative array ['propertyName' => 'propertyValue']
* #return void
* #throws ReflectionException
*/
public function setProperties($object, $properties)
{
$reflection = new ReflectionClass($object);
foreach ($properties as $name => $value) {
$reflection_property = $reflection->getProperty($name);
$reflection_property->setAccessible(true);
$reflection_property->setValue($object, $value);
}
}
Example use:
$mock = $this->createMock(MyClass::class);
$this->setProperties($mock, [
'propname1' => 'valueOfPrivateProp1',
'propname2' => 'valueOfPrivateProp2'
]);
It would be amazing if every codebase used DI and IoC, and never did stuff like this:
public function __construct(BlahClass $blah)
{
$this->protectedProperty = new FooClass($blah);
}
You can use a mock BlahClass in the constructor, sure, but then the constructor sets a protected property to something you CAN'T mock.
So you're probably thinking "Well refactor the constructor to take a FooClass instead of a BlahClass, then you don't have to instantiate the FooClass in the constructor, and you can put in a mock instead!" Well, you'd be right, if that didn't mean you would have to change every usage of the class in the entire codebase to give it a FooClass instead of a BlahClass.
Not every codebase is perfect, and sometimes you just need to get stuff done. And that means, yes, sometimes you need to break the "only test public APIs" rule.
You may want to use the ReflectionProperty as well.
$reflection = new ReflectionProperty(Foo::class, 'myProperty');
$reflection->setAccessible(true); // Do this if less than PHP8.1.
$reflection->setValue($yourMock, 'theValue');

Categories