<?php
/**
* began : July 15, 2006
* copyright : (c) 2006 - 2007, Russ Collier
* support : hide@address.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; version
* 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Or visit http://www.gnu.org/licenses/lgpl.html
*/
/**
* PHPeas_PeaUtils is a class that contains utility methods for performing
* various reflection and instrospection tasks on PHPeas.
*
* @package PHPeas
* @author russ
*/
class PHPeas_PeaUtils
{
const DESCRIBE_CLASS_KEY = '!class';
const METHOD_PREFIX_READ = 'get';
const METHOD_PREFIX_READ_BOOL = 'is';
const METHOD_PREFIX_WRITE = 'set';
/**
* Clone a pea based on the available property getters and setters.
*
* <b>Note:</b> this method creates a <b>shallow</b> clone. In other words,
* any objects referred to by the pea are shared with the clone rather than
* being cloned in turn.
*
* @param Object $peaIn The pea to clone
* @return Object The shallow copy clone of the given pea
* @throws ReflectionException
*/
public static function clonePea( $peaIn )
{
if ( !( self::isValidPea( $peaIn ) ) )
{
throw new ReflectionException( 'Could not clone given pea since given pea is invalid' );
}
$peaDescription = self::describe( $peaIn );
unset( $peaDescription[ self::DESCRIBE_CLASS_KEY ] );
$peaClassName = get_class( $peaIn );
$clonePea = new $peaClassName();
self::populate( $clonePea, $peaDescription );
return $clonePea;
}
/**
* Copy property values from the origin bean to the destination pea for
* all cases where the property names are the same.
*
* Properties that exist in the source pea, but do not exist in the
* destination pea (or are read-only in the destination pea) are silently
* ignored.
*
* <b>Note:</b> this method performs a <b>shallow</b> copy. In other words,
* any objects referred to by the source pea are shared with the
* destination pea rather than being cloned in turn.
*
* @param Object $srcPeaIn The pea to read properties from
* @param Object $destPeaIn The pea to copy properties to
* @throws ReflectionException
*/
public static function copyProperties( $srcPeaIn, $destPeaIn )
{
if ( !( self::isValidPea( $srcPeaIn ) ) )
{
throw new ReflectionException( 'Could not copy properties since given source pea is invalid' );
}
if ( !( self::isValidPea( $destPeaIn ) ) )
{
throw new ReflectionException( 'Could not copy properties since given destination pea is invalid' );
}
$srcPeaDescription = self::describe( $srcPeaIn );
unset( $srcPeaDescription[ self::DESCRIBE_CLASS_KEY ] );
foreach ( $srcPeaDescription as $propertyName => $propertyValue )
{
$destWriteMethod = self::getWriteMethod(
$destPeaIn,
$propertyName
);
if ( null != $destWriteMethod )
{
$destPeaIn->$destWriteMethod( $propertyValue );
}
}
}
/**
* Retuns an array with the property name as the index and the property
* value as the element value, for each property the given pea provides a
* read method for.
*
* There is also an element in the return array with the name of the class
* (the class name key is defined in the constant
* PHPeas_PeaUtils::DESCRIBE_CLASS_KEY).
*
* <b>Note:</b> No order is guaranteed.
*
* @param Object $peaIn The pea to describe
* @return Mixed[] Property name/value pairs
* @throws ReflectionException
*/
public static function describe( $peaIn )
{
if ( !( self::isValidPea( $peaIn ) ) )
{
throw new ReflectionException( 'Could not describe given pea since given pea is invalid' );
}
$peaDesc = array();
$peaDesc[ self::DESCRIBE_CLASS_KEY ] = 'class ' . get_class( $peaIn );
$reflectedPea = new ReflectionClass( get_class( $peaIn ) );
$readMethodPattern = '/^(' . self::METHOD_PREFIX_READ_BOOL . '|' .
self::METHOD_PREFIX_READ . ')/';
foreach ( $reflectedPea->getMethods() as $reflectedMethod )
{
if ( $reflectedMethod->isPublic() &&
preg_match( $readMethodPattern, $reflectedMethod->getName() ) )
{
// Extract the property name from the method name
$propertyName = $reflectedMethod->getName();
$propertyName = preg_replace( $readMethodPattern, '', $propertyName );
$firstLetter = $propertyName{ 0 };
$propertyName = strtolower( $firstLetter ) . substr( $propertyName, 1 );
$peaDesc[ $propertyName ] = $reflectedMethod->invoke( $peaIn, null );
}
}
$reflectedPea = null;
return $peaDesc;
}
/*
* This method violates the PHPeas Spec's security rules
* but I still think it's a cool little algorithm...
*
* Gets an array of Strings representing all names of properties,
* regardless of visibility, for the given class and all of its ancestors.
*
* <b>Note:</b> No order is guaranteed.
*
* @param String $className The name of the class to find all the
* properties for
* @return String[] An array of all property names
*
public static function getAllClassProperties( $className )
{
$properties = array();
$reflectedClass = new ReflectionClass( $className );
// If the given class has a parent class, then we need to call this
// method again and get the properties for that parent class, unless
// there is a grandparent class, in which case we recurse _again_, ad
// infinitium.
if ( $reflectedParent = $reflectedClass->getParentClass() )
{
$properties = array_merge(
$properties,
self::getAllClassProperties( $reflectedParent->getName() )
);
}
foreach ( $reflectedClass->getProperties() as $reflectedProperty )
{
$properties[] = $reflectedProperty->getName();
}
$reflectedParentClass = null;
$reflectedClass = null;
return array_unique( $properties );
}*/
/**
* Gets the method that should be used to read the given property on the
* given pea.
*
* @param Object $peaIn The pea with the property in question
* @param String $propertyNameIn The name of the property to find the
* read method for
* @return String The name of the read method, or null if not found
* @throws ReflectionException
*/
public static function getReadMethod( $peaIn, $propertyNameIn )
{
if ( !( self::isValidPea( $peaIn ) ) )
{
throw new ReflectionException( 'No read methods can be found since given pea is invalid' );
}
if ( null == $propertyNameIn || '' == $propertyNameIn )
{
throw new ReflectionException( 'No read methods can be found since given property name is invalid' );
}
$readMethod = null;
$possibleReadMethods = array();
$possibleReadMethods[] = self::METHOD_PREFIX_READ_BOOL . ucfirst( $propertyNameIn );
$possibleReadMethods[] = self::METHOD_PREFIX_READ . ucfirst( $propertyNameIn );
foreach ( $possibleReadMethods as $inferredMethodName )
{
if ( self::publicMethodExists( $peaIn, $inferredMethodName ) )
{
$readMethod = $inferredMethodName;
break;
}
}
return $readMethod;
}
/**
* Gets the method that should be used to write to the given property on
* the given pea.
*
* @param Object $peaIn The pea with the property in question
* @param String $propertyNameIn The name of the property to find the
* write method for
* @return String The name of the write method, or null if not found
* @throws ReflectionException
*/
public static function getWriteMethod( $peaIn, $propertyNameIn )
{
if ( !( self::isValidPea( $peaIn ) ) )
{
throw new ReflectionException( 'No write methods can be found since given pea is invalid' );
}
if ( null == $propertyNameIn || '' == $propertyNameIn )
{
throw new ReflectionException( 'No write methods can be found since given property name is invalid' );
}
$writeMethod = null;
$inferredMethodName = self::METHOD_PREFIX_WRITE . ucfirst( $propertyNameIn );
if ( self::publicMethodExists( $peaIn, $inferredMethodName ) )
{
$writeMethod = $inferredMethodName;
}
return $writeMethod;
}
/**
* Calls the given method name on the given pea with the given arguments.
*
* @param Object $peaIn The pea to invoke the method on
* @param String $methodNameIn The name of the method to invoke
* @param Mixed $argsIn The list of method arguments
* @return Mixed
* @throws ReflectionException
*/
public static function invokeMethod( $peaIn, $methodNameIn, $argsIn = null )
{
if ( !( self::isValidPea( $peaIn ) ) )
{
throw new ReflectionException( 'Could not invoke method on given pea since given pea is invalid' );
}
if ( null == $methodNameIn || '' == $methodNameIn )
{
throw new ReflectionException( 'Could not invoke method on given pea since given method name is invalid' );
}
$reflectedPea = new ReflectionClass( get_class( $peaIn ) );
try
{
$reflectedMethod = $reflectedPea->getMethod( $methodNameIn );
}
catch ( ReflectionException $re )
{
throw new ReflectionException( 'Could not invoke method since given method name does not exist in given pea' );
}
return $reflectedMethod->invoke( $peaIn, $argsIn );
}
/**
* Calls the given static method name from the given pea with the given
* arguments.
*
* @param Object $peaIn The pea to invoke the method from
* @param String $methodNameIn The name of the static method to invoke
* @param Mixed $argsIn The list of method arguments
* @return Mixed
* @throws ReflectionException
*/
public static function invokeStaticMethod( $peaIn, $methodNameIn, $argsIn = null )
{
if ( !( self::isValidPea( $peaIn ) ) )
{
throw new ReflectionException( 'Could not invoke static method on given pea since given pea is invalid' );
}
if ( null == $methodNameIn || '' == $methodNameIn )
{
throw new ReflectionException( 'Could not invoke static method on given pea since given method name is invalid' );
}
$reflectedPea = new ReflectionClass( get_class( $peaIn ) );
try
{
$reflectedMethod = $reflectedPea->getMethod( $methodNameIn );
}
catch ( ReflectionException $re )
{
throw new ReflectionException( 'Could not invoke static method since given method name does ' .
'not exist in given pea' );
}
if ( !( $reflectedMethod->isStatic() ) )
{
throw new ReflectionException( 'Could not invoke static method since given method is not actually static' );
}
return $reflectedMethod->invoke( null, $argsIn );
}
/**
* Populate the PHPeas properties of the specified pea, based on the
* specified name/value pairs.
*
* @param Object $peaIn The pea to populate with the given property
* values
* @param Mixed[] $propertiesIn Property name/value pairs
* @throws ReflectionException
*/
public static function populate( $peaIn, $propertiesIn )
{
if ( !( self::isValidPea( $peaIn ) ) )
{
throw new ReflectionException( 'Could not populate given pea since given pea is invalid' );
}
if ( null == $propertiesIn || !( is_array( $propertiesIn ) ) )
{
throw new ReflectionException( 'Could not populate given pea since given properties array is invalid' );
}
foreach ( $propertiesIn as $propertyName => $propertyValue )
{
$methodName = self::getWriteMethod( $peaIn, $propertyName );
if ( null != $methodName )
{
$peaIn->$methodName( $propertyValue );
}
}
}
/**
* Returns true or false depending on whether or not the given pea is
* valid. Pea validity is defined as follows:
* <br/>
* Not null<br />
* Is an object<br />
*
* @param Object $peaIn The pea to test for validity
* @return Boolean True if the given pea is valid, otherwise False
*/
public static function isValidPea( $peaIn )
{
$validPea = false;
if ( null != $peaIn && is_object( $peaIn ) )
{
$class = new ReflectionClass( get_class( $peaIn ) );
$constructor = $class->getConstructor();
if ( ( null == $constructor ) ||
( $constructor->isConstructor() &&
$constructor->isPublic() &&
0 == $constructor->getNumberOfRequiredParameters() ) )
{
$validPea = true;
}
}
return $validPea;
}
/**
* Returns true or false depending on whether or not the given method name
* exists and is a public method on the given pea.
*
* @param Object $peaIn The pea to check the method on
* @param String $methodNameIn The method name to test
* @return Boolean True if the given pea is valid, otherwise False
* @throws ReflectionException
*/
public static function publicMethodExists( $peaIn, $methodNameIn )
{
$result = false;
$reflectedClass = new ReflectionClass( get_class( $peaIn ) );
$reflectedMethod = null;
try
{
$reflectedMethod = $reflectedClass->getMethod( $methodNameIn );
}
catch ( ReflectionException $re )
{
$reflectedMethod = null;
}
if ( null != $reflectedMethod && $reflectedMethod->isPublic() )
{
$result = true;
}
$reflectedMethod = null;
$reflectedClass = null;
return $result;
}
}
?>