TYPO3 API  SVNRelease
Service.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2009 Christopher Hlubek <hlubek@networkteam.com>
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *
00017 *  This script is distributed in the hope that it will be useful,
00018 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 *  GNU General Public License for more details.
00021 *
00022 *  This copyright notice MUST APPEAR in all copies of the script!
00023 ***************************************************************/
00024 
00025 /**
00026  * A backport of the FLOW3 reflection service for aquiring reflection based information.
00027  * Most of the code is based on the FLOW3 reflection service.
00028  *
00029  * @package Extbase
00030  * @subpackage Reflection
00031  * @version $Id: Service.php 1789 2010-01-18 21:31:59Z jocrau $
00032  * @api
00033  */
00034 class Tx_Extbase_Reflection_Service implements t3lib_Singleton {
00035 
00036     /**
00037      * Whether this service has been initialized.
00038      *
00039      * @var boolean
00040      */
00041     protected $initialized = FALSE;
00042 
00043     /**
00044      * @var t3lib_cache_frontend_VariableFrontend
00045      */
00046     protected $dataCache;
00047 
00048     /**
00049      * Whether class alterations should be detected on each initialization.
00050      *
00051      * @var boolean
00052      */
00053     protected $detectClassChanges = FALSE;
00054 
00055     /**
00056      * All available class names to consider. Class name = key, value is the
00057      * UNIX timestamp the class was reflected.
00058      *
00059      * @var array
00060      */
00061     protected $reflectedClassNames = array();
00062 
00063     /**
00064      * Array of tags and the names of classes which are tagged with them.
00065      *
00066      * @var array
00067      */
00068     protected $taggedClasses = array();
00069 
00070     /**
00071      * Array of class names and their tags and values.
00072      *
00073      * @var array
00074      */
00075     protected $classTagsValues = array();
00076 
00077     /**
00078      * Array of class names, method names and their tags and values.
00079      *
00080      * @var array
00081      */
00082     protected $methodTagsValues = array();
00083 
00084     /**
00085      * Array of class names, method names, their parameters and additional
00086      * information about the parameters.
00087      *
00088      * @var array
00089      */
00090     protected $methodParameters = array();
00091 
00092     /**
00093      * Array of class names and names of their properties.
00094      *
00095      * @var array
00096      */
00097     protected $classPropertyNames = array();
00098 
00099     /**
00100      * Array of class names, property names and their tags and values.
00101      *
00102      * @var array
00103      */
00104     protected $propertyTagsValues = array();
00105 
00106     /**
00107      * List of tags which are ignored while reflecting class and method annotations.
00108      *
00109      * @var array
00110      */
00111     protected $ignoredTags = array('package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const');
00112 
00113     /**
00114      * Indicates whether the Reflection cache needs to be updated.
00115      *
00116      * This flag needs to be set as soon as new Reflection information was
00117      * created.
00118      *
00119      * @see reflectClass()
00120      * @see getMethodReflection()
00121      *
00122      * @var boolean
00123      */
00124     protected $dataCacheNeedsUpdate = FALSE;
00125 
00126     /**
00127      * Local cache for Class schemata
00128      * @var array
00129      */
00130     protected $classSchemata = array();
00131 
00132     /**
00133      * @var Tx_Extbase_Configuration_ConfigurationManagerInterface
00134      */
00135     protected $configurationManager;
00136 
00137     /**
00138      * @var string
00139      */
00140     protected $cacheIdentifier;
00141 
00142     /**
00143      * @param Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager
00144      * @return void
00145      */
00146     public function injectConfigurationManager(Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager) {
00147         $this->configurationManager = $configurationManager;
00148     }
00149 
00150     /**
00151      * Sets the data cache.
00152      *
00153      * The cache must be set before initializing the Reflection Service.
00154      *
00155      * @param t3lib_cache_frontend_VariableFrontend $dataCache Cache for the Reflection service
00156      * @return void
00157      */
00158     public function setDataCache(t3lib_cache_frontend_VariableFrontend $dataCache) {
00159         $this->dataCache = $dataCache;
00160     }
00161 
00162     /**
00163      * Initializes this service
00164      *
00165      * @return void
00166      */
00167     public function initialize() {
00168         if ($this->initialized) {
00169             throw new Tx_Extbase_Reflection_Exception('The Reflection Service can only be initialized once.', 1232044696);
00170         }
00171         $frameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
00172         $this->cacheIdentifier = 'ReflectionData_' . $frameworkConfiguration['extensionName'];
00173 
00174         $this->loadFromCache();
00175 
00176         $this->initialized = TRUE;
00177     }
00178 
00179     /**
00180      * Returns whether the Reflection Service is initialized.
00181      *
00182      * @return boolean true if the Reflection Service is initialized, otherwise false
00183      */
00184     public function isInitialized() {
00185         return $this->initialized;
00186     }
00187 
00188     /**
00189      * Shuts the Reflection Service down.
00190      *
00191      * @return void
00192      */
00193     public function shutdown() {
00194         if ($this->dataCacheNeedsUpdate) {
00195             $this->saveToCache();
00196         }
00197         $this->initialized = FALSE;
00198     }
00199 
00200     /**
00201      * Returns the names of all properties of the specified class
00202      *
00203      * @param string $className Name of the class to return the property names of
00204      * @return array An array of property names or an empty array if none exist
00205      */
00206     public function getClassPropertyNames($className) {
00207         if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
00208         return (isset($this->classPropertyNames[$className])) ? $this->classPropertyNames[$className] : array();
00209     }
00210 
00211     /**
00212      * Returns the class schema for the given class
00213      *
00214      * @param mixed $classNameOrObject The class name or an object
00215      * @return Tx_Extbase_Reflection_ClassSchema
00216      * @author Karsten Dambekalns <karsten@typo3.org>
00217      */
00218     public function getClassSchema($classNameOrObject) {
00219         $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
00220         if (isset($this->classSchemata[$className])) {
00221             return $this->classSchemata[$className];
00222         } else {
00223             return $this->buildClassSchema($className);
00224         }
00225 
00226     }
00227 
00228     /**
00229      * Returns all tags and their values the specified method is tagged with
00230      *
00231      * @param string $className Name of the class containing the method
00232      * @param string $methodName Name of the method to return the tags and values of
00233      * @return array An array of tags and their values or an empty array of no tags were found
00234      */
00235     public function getMethodTagsValues($className, $methodName) {
00236         if (!isset($this->methodTagsValues[$className][$methodName])) {
00237             $this->methodTagsValues[$className][$methodName] = array();
00238             $method = $this->getMethodReflection($className, $methodName);
00239             foreach ($method->getTagsValues() as $tag => $values) {
00240                 if (array_search($tag, $this->ignoredTags) === FALSE) {
00241                     $this->methodTagsValues[$className][$methodName][$tag] = $values;
00242                 }
00243             }
00244         }
00245         return $this->methodTagsValues[$className][$methodName];
00246     }
00247 
00248 
00249     /**
00250      * Returns an array of parameters of the given method. Each entry contains
00251      * additional information about the parameter position, type hint etc.
00252      *
00253      * @param string $className Name of the class containing the method
00254      * @param string $methodName Name of the method to return parameter information of
00255      * @return array An array of parameter names and additional information or an empty array of no parameters were found
00256      */
00257     public function getMethodParameters($className, $methodName) {
00258         if (!isset($this->methodParameters[$className][$methodName])) {
00259             $method = $this->getMethodReflection($className, $methodName);
00260             $this->methodParameters[$className][$methodName] = array();
00261             foreach($method->getParameters() as $parameterPosition => $parameter) {
00262                 $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
00263             }
00264         }
00265         return $this->methodParameters[$className][$methodName];
00266     }
00267 
00268     /**
00269      * Returns all tags and their values the specified class property is tagged with
00270      *
00271      * @param string $className Name of the class containing the property
00272      * @param string $propertyName Name of the property to return the tags and values of
00273      * @return array An array of tags and their values or an empty array of no tags were found
00274      */
00275     public function getPropertyTagsValues($className, $propertyName) {
00276         if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
00277         if (!isset($this->propertyTagsValues[$className])) return array();
00278         return (isset($this->propertyTagsValues[$className][$propertyName])) ? $this->propertyTagsValues[$className][$propertyName] : array();
00279     }
00280 
00281     /**
00282      * Returns the values of the specified class property tag
00283      *
00284      * @param string $className Name of the class containing the property
00285      * @param string $propertyName Name of the tagged property
00286      * @param string $tag Tag to return the values of
00287      * @return array An array of values or an empty array if the tag was not found
00288      * @author Robert Lemke <robert@typo3.org>
00289      * @api
00290      */
00291     public function getPropertyTagValues($className, $propertyName, $tag) {
00292         if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
00293         if (!isset($this->propertyTagsValues[$className][$propertyName])) return array();
00294         return (isset($this->propertyTagsValues[$className][$propertyName][$tag])) ? $this->propertyTagsValues[$className][$propertyName][$tag] : array();
00295     }
00296 
00297     /**
00298      * Tells if the specified class is known to this reflection service and
00299      * reflection information is available.
00300      *
00301      * @param string $className Name of the class
00302      * @return boolean If the class is reflected by this service
00303      * @author Robert Lemke <robert@typo3.org>
00304      * @api
00305      */
00306     public function isClassReflected($className) {
00307         return isset($this->reflectedClassNames[$className]);
00308     }
00309 
00310     /**
00311      * Tells if the specified class is tagged with the given tag
00312      *
00313      * @param string $className Name of the class
00314      * @param string $tag Tag to check for
00315      * @return boolean TRUE if the class is tagged with $tag, otherwise FALSE
00316      * @author Robert Lemke <robert@typo3.org>
00317      * @api
00318      */
00319     public function isClassTaggedWith($className, $tag) {
00320         if ($this->initialized === FALSE) return FALSE;
00321         if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
00322         if (!isset($this->classTagsValues[$className])) return FALSE;
00323         return isset($this->classTagsValues[$className][$tag]);
00324     }
00325 
00326     /**
00327      * Tells if the specified class property is tagged with the given tag
00328      *
00329      * @param string $className Name of the class
00330      * @param string $propertyName Name of the property
00331      * @param string $tag Tag to check for
00332      * @return boolean TRUE if the class property is tagged with $tag, otherwise FALSE
00333      * @author Robert Lemke <robert@typo3.org>
00334      * @api
00335      */
00336     public function isPropertyTaggedWith($className, $propertyName, $tag) {
00337         if (!isset($this->reflectedClassNames[$className])) $this->reflectClass($className);
00338         if (!isset($this->propertyTagsValues[$className])) return FALSE;
00339         if (!isset($this->propertyTagsValues[$className][$propertyName])) return FALSE;
00340         return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
00341     }
00342 
00343     /**
00344      * Reflects the given class and stores the results in this service's properties.
00345      *
00346      * @param string $className Full qualified name of the class to reflect
00347      * @return void
00348      */
00349     protected function reflectClass($className) {
00350         $class = new Tx_Extbase_Reflection_ClassReflection($className);
00351         $this->reflectedClassNames[$className] = time();
00352 
00353         foreach ($class->getTagsValues() as $tag => $values) {
00354             if (array_search($tag, $this->ignoredTags) === FALSE) {
00355                 $this->taggedClasses[$tag][] = $className;
00356                 $this->classTagsValues[$className][$tag] = $values;
00357             }
00358         }
00359 
00360         foreach ($class->getProperties() as $property) {
00361             $propertyName = $property->getName();
00362             $this->classPropertyNames[$className][] = $propertyName;
00363 
00364             foreach ($property->getTagsValues() as $tag => $values) {
00365                 if (array_search($tag, $this->ignoredTags) === FALSE) {
00366                     $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
00367                 }
00368             }
00369         }
00370 
00371         foreach ($class->getMethods() as $method) {
00372             $methodName = $method->getName();
00373             foreach ($method->getTagsValues() as $tag => $values) {
00374                 if (array_search($tag, $this->ignoredTags) === FALSE) {
00375                     $this->methodTagsValues[$className][$methodName][$tag] = $values;
00376                 }
00377             }
00378 
00379             foreach ($method->getParameters() as $parameterPosition => $parameter) {
00380                 $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
00381             }
00382         }
00383         ksort($this->reflectedClassNames);
00384 
00385         $this->dataCacheNeedsUpdate = TRUE;
00386     }
00387 
00388     /**
00389      * Builds class schemata from classes annotated as entities or value objects
00390      *
00391      * @return Tx_Extbase_Reflection_ClassSchema The class schema
00392      */
00393     protected function buildClassSchema($className) {
00394         if (!class_exists($className)) {
00395             return NULL;
00396         }
00397         $classSchema = new Tx_Extbase_Reflection_ClassSchema($className);
00398         if (is_subclass_of($className, 'Tx_Extbase_DomainObject_AbstractEntity')) {
00399             $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema::MODELTYPE_ENTITY);
00400 
00401             $possibleRepositoryClassName = str_replace('_Model_', '_Repository_', $className) . 'Repository';
00402             if (class_exists($possibleRepositoryClassName)) {
00403                 $classSchema->setAggregateRoot(TRUE);
00404             }
00405         } elseif (is_subclass_of($className, 'Tx_Extbase_DomainObject_AbstractValueObject')) {
00406             $classSchema->setModelType(Tx_Extbase_Reflection_ClassSchema::MODELTYPE_VALUEOBJECT);
00407         } else {
00408             return NULL;
00409         }
00410 
00411         foreach ($this->getClassPropertyNames($className) as $propertyName) {
00412             if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
00413                 $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
00414                 $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
00415             }
00416             if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
00417                 $classSchema->setUUIDPropertyName($propertyName);
00418             }
00419             if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
00420                 $classSchema->markAsIdentityProperty($propertyName);
00421             }
00422         }
00423         $this->classSchemata[$className] = $classSchema;
00424         $this->dataCacheNeedsUpdate = TRUE;
00425         return $classSchema;
00426     }
00427 
00428     /**
00429      * Converts the given parameter reflection into an information array
00430      *
00431      * @param ReflectionParameter $parameter The parameter to reflect
00432      * @return array Parameter information array
00433      */
00434     protected function convertParameterReflectionToArray(ReflectionParameter $parameter, $parameterPosition, ReflectionMethod $method = NULL) {
00435         $parameterInformation = array(
00436             'position' => $parameterPosition,
00437             'byReference' => $parameter->isPassedByReference() ? TRUE : FALSE,
00438             'array' => $parameter->isArray() ? TRUE : FALSE,
00439             'optional' => $parameter->isOptional() ? TRUE : FALSE,
00440             'allowsNull' => $parameter->allowsNull() ? TRUE : FALSE
00441         );
00442 
00443         $parameterClass = $parameter->getClass();
00444         $parameterInformation['class'] = ($parameterClass !== NULL) ? $parameterClass->getName() : NULL;
00445         if ($parameter->isDefaultValueAvailable()) {
00446             $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
00447         }
00448         if ($parameterClass !== NULL) {
00449             $parameterInformation['type'] = $parameterClass->getName();
00450         } elseif ($method !== NULL) {
00451             $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
00452             if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
00453                 $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
00454                 if (count($explodedParameters) >= 2) {
00455                     $parameterInformation['type'] = $explodedParameters[0];
00456                 }
00457             }
00458         }
00459         if (isset($parameterInformation['type']) && $parameterInformation['type']{0} === '\\') {
00460             $parameterInformation['type'] = substr($parameterInformation['type'], 1);
00461         }
00462         return $parameterInformation;
00463     }
00464 
00465     /**
00466      * Returns the Reflection of a method.
00467      *
00468      * @param string $className Name of the class containing the method
00469      * @param string $methodName Name of the method to return the Reflection for
00470      * @return Tx_Extbase_Reflection_MethodReflection the method Reflection object
00471      */
00472     protected function getMethodReflection($className, $methodName) {
00473         if (!isset($this->methodReflections[$className][$methodName])) {
00474             $this->methodReflections[$className][$methodName] = new Tx_Extbase_Reflection_MethodReflection($className, $methodName);
00475             $this->dataCacheNeedsUpdate = TRUE;
00476         }
00477         return $this->methodReflections[$className][$methodName];
00478     }
00479 
00480     /**
00481      * Tries to load the reflection data from this service's cache.
00482      *
00483      * @return void
00484      */
00485     protected function loadFromCache() {
00486         if ($this->dataCache->has($this->cacheIdentifier)) {
00487             $data = $this->dataCache->get($this->cacheIdentifier);
00488             foreach ($data as $propertyName => $propertyValue) {
00489                 $this->$propertyName = $propertyValue;
00490             }
00491         }
00492     }
00493 
00494     /**
00495      * Exports the internal reflection data into the ReflectionData cache.
00496      *
00497      * @return void
00498      */
00499     protected function saveToCache() {
00500         if (!is_object($this->dataCache)) {
00501             throw new Tx_Extbase_Reflection_Exception(
00502                 'A cache must be injected before initializing the Reflection Service.',
00503                 1232044697
00504             );
00505         }
00506 
00507         $data = array();
00508         $propertyNames = array(
00509             'reflectedClassNames',
00510             'classPropertyNames',
00511             'classTagsValues',
00512             'methodTagsValues',
00513             'methodParameters',
00514             'propertyTagsValues',
00515             'taggedClasses',
00516             'classSchemata'
00517         );
00518         foreach ($propertyNames as $propertyName) {
00519             $data[$propertyName] = $this->$propertyName;
00520         }
00521         $this->dataCache->set($this->cacheIdentifier, $data);
00522     }
00523 
00524 }
00525 ?>