2015-08-17 17:00:26 -07:00
< ? php
/**
* Zend Framework ( http :// framework . zend . com / )
*
* @ link http :// github . com / zendframework / zf2 for the canonical source repository
* @ copyright Copyright ( c ) 2005 - 2015 Zend Technologies USA Inc . ( http :// www . zend . com )
* @ license http :// framework . zend . com / license / new - bsd New BSD License
*/
2015-10-08 11:40:12 -07:00
namespace Zend\Hydrator ;
2015-08-17 17:00:26 -07:00
use Traversable ;
use Zend\Stdlib\ArrayUtils ;
class ClassMethods extends AbstractHydrator implements HydratorOptionsInterface
{
/**
* Holds the names of the methods used for hydration , indexed by class :: property name ,
* false if the hydration method is not callable / usable for hydration purposes
*
* @ var string [] | bool []
*/
2015-10-08 11:40:12 -07:00
private $hydrationMethodsCache = [];
2015-08-17 17:00:26 -07:00
/**
* A map of extraction methods to property name to be used during extraction , indexed
* by class name and method name
*
* @ var string [][]
*/
2015-10-08 11:40:12 -07:00
private $extractionMethodsCache = [];
2015-08-17 17:00:26 -07:00
/**
* Flag defining whether array keys are underscore - separated ( true ) or camel case ( false )
*
* @ var bool
*/
protected $underscoreSeparatedKeys = true ;
/**
2015-10-08 11:40:12 -07:00
* @ var Filter\FilterInterface
2015-08-17 17:00:26 -07:00
*/
private $callableMethodFilter ;
/**
* Define if extract values will use camel case or name with underscore
* @ param bool | array $underscoreSeparatedKeys
*/
public function __construct ( $underscoreSeparatedKeys = true )
{
parent :: __construct ();
$this -> setUnderscoreSeparatedKeys ( $underscoreSeparatedKeys );
2015-10-08 11:40:12 -07:00
$this -> callableMethodFilter = new Filter\OptionalParametersFilter ();
2015-08-17 17:00:26 -07:00
2015-10-08 11:40:12 -07:00
$this -> filterComposite -> addFilter ( 'is' , new Filter\IsFilter ());
$this -> filterComposite -> addFilter ( 'has' , new Filter\HasFilter ());
$this -> filterComposite -> addFilter ( 'get' , new Filter\GetFilter ());
$this -> filterComposite -> addFilter (
'parameter' ,
new Filter\OptionalParametersFilter (),
Filter\FilterComposite :: CONDITION_AND
);
2015-08-17 17:00:26 -07:00
}
/**
* @ param array | Traversable $options
* @ return ClassMethods
* @ throws Exception\InvalidArgumentException
*/
public function setOptions ( $options )
{
if ( $options instanceof Traversable ) {
$options = ArrayUtils :: iteratorToArray ( $options );
} elseif ( ! is_array ( $options )) {
throw new Exception\InvalidArgumentException (
'The options parameter must be an array or a Traversable'
);
}
if ( isset ( $options [ 'underscoreSeparatedKeys' ])) {
$this -> setUnderscoreSeparatedKeys ( $options [ 'underscoreSeparatedKeys' ]);
}
return $this ;
}
/**
* @ param bool $underscoreSeparatedKeys
* @ return ClassMethods
*/
public function setUnderscoreSeparatedKeys ( $underscoreSeparatedKeys )
{
$this -> underscoreSeparatedKeys = ( bool ) $underscoreSeparatedKeys ;
if ( $this -> underscoreSeparatedKeys ) {
2015-10-08 11:40:12 -07:00
$this -> setNamingStrategy ( new NamingStrategy\UnderscoreNamingStrategy );
} elseif ( $this -> getNamingStrategy () instanceof NamingStrategy\UnderscoreNamingStrategy ) {
2015-08-17 17:00:26 -07:00
$this -> removeNamingStrategy ();
}
return $this ;
}
/**
* @ return bool
*/
public function getUnderscoreSeparatedKeys ()
{
return $this -> underscoreSeparatedKeys ;
}
/**
* Extract values from an object with class methods
*
* Extracts the getter / setter of the given $object .
*
* @ param object $object
* @ return array
* @ throws Exception\BadMethodCallException for a non - object $object
*/
public function extract ( $object )
{
if ( ! is_object ( $object )) {
throw new Exception\BadMethodCallException ( sprintf (
'%s expects the provided $object to be a PHP object)' ,
__METHOD__
));
}
$objectClass = get_class ( $object );
// reset the hydrator's hydrator's cache for this object, as the filter may be per-instance
2015-10-08 11:40:12 -07:00
if ( $object instanceof Filter\FilterProviderInterface ) {
2015-08-17 17:00:26 -07:00
$this -> extractionMethodsCache [ $objectClass ] = null ;
}
// pass 1 - finding out which properties can be extracted, with which methods (populate hydration cache)
if ( ! isset ( $this -> extractionMethodsCache [ $objectClass ])) {
2015-10-08 11:40:12 -07:00
$this -> extractionMethodsCache [ $objectClass ] = [];
2015-08-17 17:00:26 -07:00
$filter = $this -> filterComposite ;
$methods = get_class_methods ( $object );
2015-10-08 11:40:12 -07:00
if ( $object instanceof Filter\FilterProviderInterface ) {
$filter = new Filter\FilterComposite (
[ $object -> getFilter ()],
[ new Filter\MethodMatchFilter ( 'getFilter' )]
2015-08-17 17:00:26 -07:00
);
}
foreach ( $methods as $method ) {
$methodFqn = $objectClass . '::' . $method ;
if ( ! ( $filter -> filter ( $methodFqn ) && $this -> callableMethodFilter -> filter ( $methodFqn ))) {
continue ;
}
$attribute = $method ;
if ( strpos ( $method , 'get' ) === 0 ) {
$attribute = substr ( $method , 3 );
if ( ! property_exists ( $object , $attribute )) {
$attribute = lcfirst ( $attribute );
}
}
$this -> extractionMethodsCache [ $objectClass ][ $method ] = $attribute ;
}
}
2015-10-08 11:40:12 -07:00
$values = [];
2015-08-17 17:00:26 -07:00
// pass 2 - actually extract data
foreach ( $this -> extractionMethodsCache [ $objectClass ] as $methodName => $attributeName ) {
$realAttributeName = $this -> extractName ( $attributeName , $object );
$values [ $realAttributeName ] = $this -> extractValue ( $realAttributeName , $object -> $methodName (), $object );
}
return $values ;
}
/**
* Hydrate an object by populating getter / setter methods
*
* Hydrates an object by getter / setter methods of the object .
*
* @ param array $data
* @ param object $object
* @ return object
* @ throws Exception\BadMethodCallException for a non - object $object
*/
public function hydrate ( array $data , $object )
{
if ( ! is_object ( $object )) {
throw new Exception\BadMethodCallException ( sprintf (
'%s expects the provided $object to be a PHP object)' ,
__METHOD__
));
}
$objectClass = get_class ( $object );
foreach ( $data as $property => $value ) {
$propertyFqn = $objectClass . '::$' . $property ;
if ( ! isset ( $this -> hydrationMethodsCache [ $propertyFqn ])) {
$setterName = 'set' . ucfirst ( $this -> hydrateName ( $property , $data ));
2015-10-08 11:40:12 -07:00
$this -> hydrationMethodsCache [ $propertyFqn ] = is_callable ([ $object , $setterName ])
2015-08-17 17:00:26 -07:00
? $setterName
: false ;
}
if ( $this -> hydrationMethodsCache [ $propertyFqn ]) {
$object -> { $this -> hydrationMethodsCache [ $propertyFqn ]}( $this -> hydrateValue ( $property , $value , $data ));
}
}
return $object ;
}
/**
* { @ inheritDoc }
*/
2015-10-08 11:40:12 -07:00
public function addFilter ( $name , $filter , $condition = Filter\FilterComposite :: CONDITION_OR )
2015-08-17 17:00:26 -07:00
{
$this -> resetCaches ();
return parent :: addFilter ( $name , $filter , $condition );
}
/**
* { @ inheritDoc }
*/
public function removeFilter ( $name )
{
$this -> resetCaches ();
return parent :: removeFilter ( $name );
}
/**
* { @ inheritDoc }
*/
2015-10-08 11:40:12 -07:00
public function setNamingStrategy ( NamingStrategy\NamingStrategyInterface $strategy )
2015-08-17 17:00:26 -07:00
{
$this -> resetCaches ();
return parent :: setNamingStrategy ( $strategy );
}
/**
* { @ inheritDoc }
*/
public function removeNamingStrategy ()
{
$this -> resetCaches ();
return parent :: removeNamingStrategy ();
}
/**
* Reset all local hydration / extraction caches
*/
private function resetCaches ()
{
2015-10-08 11:40:12 -07:00
$this -> hydrationMethodsCache = $this -> extractionMethodsCache = [];
2015-08-17 17:00:26 -07:00
}
}