Skip to content

Commit

Permalink
Use ParameterReflection to determine null allowed for property
Browse files Browse the repository at this point in the history
Summary:
Implements a method to check if property setter is allowed
to receive a null value. Also strikes the "does not use
Reflection" claim from the README.

ParameterReflection should be sufficiently quick.

Reviewers: dkd-ebert

Reviewed By: dkd-ebert

Subscribers: dkd-ebert

Differential Revision: https://phabricator.dkd.de/D984
  • Loading branch information
NamelessCoder committed Mar 6, 2015
1 parent 69e6c08 commit db8e84f
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ single method.

`PopulateTrait` is great because it:

* Is simple and fast - no use of Reflection, code generation and such
* Is simple and fast - no use of code generation and such
* Uses getters and setters - always respects your public API
* Does not add overloaded methods - avoids magic behavior
* Is bi-directional - populates *and* exports properties
Expand Down
45 changes: 37 additions & 8 deletions src/PopulateTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ public function populateWithClones($source, array $propertyNameMap = array(), $o
* @param boolean $cloneObjects If <code>true</code> will use
* <code>clone</coode> on any object instances
* @throws Exception Thrown on invalid or unsupported input data
* or problems while setting properties
* @throws AccessException Thrown on problems while setting properties
*/
private function populateInternal($source, array $propertyNameMap, $onlyMappedProperties, $cloneObjects)
{
// decide where values come from or throw Exception if not retrievable
if ($source instanceof PopulateInterface) {
$source = $source->exportGettableProperties($propertyNameMap, $onlyMappedProperties);
$data = $source->exportGettableProperties($propertyNameMap, $onlyMappedProperties);
// ignore setter presence failures when values come from another object
} elseif (!$onlyMappedProperties && $source instanceof \ArrayAccess && !$source instanceof \Iterator) {
// fail if passing ArrayAccess without Iterator without explicit property list:
Expand All @@ -125,24 +125,39 @@ private function populateInternal($source, array $propertyNameMap, $onlyMappedPr
'Invalid source type: ' . gettype($source),
1422045180
);
} else {
$data = $source;
}

$propertyNameMap = $this->convertPropertyMap($propertyNameMap);

// loop values, skipping mapped properties, use Trait's internal setter to set value
if (!$onlyMappedProperties) {
foreach ($source as $propertyName => $propertyValue) {
foreach ($data as $propertyName => $propertyValue) {
if (!isset($propertyNameMap[$propertyName])) {
$this->setPopulatedProperty($propertyName, $propertyValue, $cloneObjects);
try {
// Note: $propertyValue is re-assigned directly from $data, again. We do
// this to make sure that if an Iterator+ArrayAccess instance is given,
// then the value returned from the ArrayAccess will replace the value
// returned from the Iterator portion. Re-assigning the variable this
// way looks redundant but serves a purpose.
$propertyValue = $data[$propertyName];
$this->setPopulatedProperty($propertyName, $propertyValue, $cloneObjects);
} catch (AccessException $error) {
// source was another Populate; $data may contain gettable but unsettable properties.
if (!$source instanceof PopulateInterface) {
throw $error;
}
}
}
}
}

// loop the mapped properties last to ensure mapped names override default names
foreach ($propertyNameMap as $sourcePropertyName => $destinationPropertyName) {
// only populate properties which were passed in source values
if (isset($source[$sourcePropertyName])) {
$propertyValue = $source[$sourcePropertyName];
if (isset($data[$sourcePropertyName])) {
$propertyValue = $data[$sourcePropertyName];
$this->setPopulatedProperty($destinationPropertyName, $propertyValue, $cloneObjects);
}
}
Expand Down Expand Up @@ -276,11 +291,11 @@ private function determinePropertyAccessFunctionName($propertyName, $method)
*/
private function setPopulatedProperty($propertyName, $value, $cloneObjects)
{
if ($value !== null) {
$method = $this->determinePropertyAccessFunctionName($propertyName, 'set');
if ($value !== null || $this->determineMethodParameterAllowsNull($method)) {
if ($cloneObjects && is_object($value)) {
$value = clone $value;
}
$method = $this->determinePropertyAccessFunctionName($propertyName, 'set');
$this->$method($value);
}
}
Expand Down Expand Up @@ -324,4 +339,18 @@ private function convertPropertyMap(array $propertyNameMap)
}
return $rebuilt;
}

/**
* Returns <code>true</code> if the method designated in $methodName
* supports <code>null</code> as value of the first/only parameter.
* Used when checking if a setter method should allow <code>null</code>.
*
* @param string $methodName
* @return boolean
*/
private function determineMethodParameterAllowsNull($methodName)
{
$parameter = new \ReflectionParameter(array($this, $methodName), 0);
return $parameter->allowsNull();
}
}

0 comments on commit db8e84f

Please sign in to comment.