Location: PHPKode > scripts > PHPeas > phpeas/src/PHPeas/PeaUtils.php
<?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;
    }
}

?>
Return current item: PHPeas