Skip to content
This repository has been archived by the owner on Mar 1, 2023. It is now read-only.

Commit

Permalink
[TASK] Introduce ReflectionService to improve performance (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
romm committed Mar 10, 2017
1 parent 15b162a commit 3e8fd0a
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 34 deletions.
119 changes: 119 additions & 0 deletions Classes/Core/Service/ReflectionService.php
@@ -0,0 +1,119 @@
<?php
/*
* 2017 Romain CANON <romain.hydrocanon@gmail.com>
*
* This file is part of the TYPO3 Configuration Object project.
* It is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, either
* version 3 of the License, or any later version.
*
* For the full copyright and license information, see:
* http://www.gnu.org/licenses/gpl-3.0.html
*/

namespace Romm\ConfigurationObject\Core\Service;

use Romm\ConfigurationObject\Exceptions\PropertyNotAccessibleException;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Reflection\ClassReflection;
use TYPO3\CMS\Extbase\Reflection\PropertyReflection;

/**
* An abstraction for class reflection, which is used a lot by this API, to
* reduce performance impact.
*/
class ReflectionService implements SingletonInterface
{
/**
* @var ReflectionService
*/
private static $instance;

/**
* @var ClassReflection[]
*/
protected $classReflection = [];

/**
* @var PropertyReflection[]
*/
protected $classAccessibleProperties = [];

/**
* @return ReflectionService
*/
public static function get()
{
if (null === self::$instance) {
self::$instance = GeneralUtility::makeInstance(self::class);
}

return self::$instance;
}

/**
* @param string $className
* @return ClassReflection
*/
public function getClassReflection($className)
{
if (false === isset($this->classReflection[$className])) {
$this->classReflection[$className] = GeneralUtility::makeInstance(ClassReflection::class, $className);
}

return $this->classReflection[$className];
}

/**
* @param string $className
* @return PropertyReflection
*/
public function getAccessibleProperties($className)
{
if (false === isset($this->classAccessibleProperties[$className])) {
$this->classAccessibleProperties[$className] = [];

$classReflection = $this->getClassReflection($className);
$properties = $classReflection->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED);

foreach ($properties as $property) {
$this->classAccessibleProperties[$className][$property->getName()] = $property;
}
}

return $this->classAccessibleProperties[$className];
}

/**
* @param string $className
* @param string $propertyName
* @return bool
*/
public function isClassPropertyAccessible($className, $propertyName)
{
$accessibleProperties = $this->getAccessibleProperties($className);

return isset($accessibleProperties[$propertyName]);
}

/**
* @param string $className
* @param string $propertyName
* @return PropertyReflection
* @throws PropertyNotAccessibleException
*/
public function getClassAccessibleProperty($className, $propertyName)
{
if (false === $this->isClassPropertyAccessible($className, $propertyName)) {
throw new PropertyNotAccessibleException(
"Property $className::$propertyName is not accessible!",
1489149795
);
}

$accessibleProperties = $this->getAccessibleProperties($className);

return $accessibleProperties[$propertyName];
}
}
11 changes: 2 additions & 9 deletions Classes/Service/ServiceFactory.php
Expand Up @@ -14,6 +14,7 @@
namespace Romm\ConfigurationObject\Service;

use Romm\ConfigurationObject\Core\Core;
use Romm\ConfigurationObject\Core\Service\ReflectionService;
use Romm\ConfigurationObject\Exceptions\DuplicateEntryException;
use Romm\ConfigurationObject\Exceptions\EntryNotFoundException;
use Romm\ConfigurationObject\Exceptions\Exception;
Expand All @@ -24,8 +25,6 @@
use Romm\ConfigurationObject\Service\DataTransferObject\AbstractServiceDTO;
use Romm\ConfigurationObject\Service\Event\ServiceEventInterface;
use Romm\ConfigurationObject\Traits\InternalVariablesTrait;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Reflection\ClassReflection;

/**
* This class will handle the several services which will be used for a
Expand Down Expand Up @@ -74,11 +73,6 @@ class ServiceFactory
*/
protected $servicesEvents = [];

/**
* @var ClassReflection[]
*/
protected $serviceClassReflection = [];

/**
* @var string
*/
Expand Down Expand Up @@ -341,8 +335,7 @@ protected function checkServiceEvent($serviceEvent)
protected function checkServiceEventMethodName($serviceEvent, $eventMethodName)
{
if (false === in_array($eventMethodName, self::$servicesChecked[$serviceEvent])) {
/** @var ClassReflection $eventClassReflection */
$eventClassReflection = GeneralUtility::makeInstance(ClassReflection::class, $serviceEvent);
$eventClassReflection = ReflectionService::get()->getClassReflection($serviceEvent);
self::$servicesChecked[$serviceEvent][] = $eventMethodName;

if (false === $eventClassReflection->hasMethod($eventMethodName)) {
Expand Down
32 changes: 7 additions & 25 deletions Classes/Traits/ConfigurationObject/MagicMethodsTrait.php
Expand Up @@ -13,10 +13,9 @@

namespace Romm\ConfigurationObject\Traits\ConfigurationObject;

use Romm\ConfigurationObject\Core\Service\ReflectionService;
use Romm\ConfigurationObject\Exceptions\MethodNotFoundException;
use Romm\ConfigurationObject\Exceptions\PropertyNotAccessibleException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Reflection\ClassReflection;

/**
* This trait will implement magic setters and getters for accessible properties
Expand All @@ -30,14 +29,6 @@
trait MagicMethodsTrait
{

/**
* Contains the list of the accessible properties for the instances of this
* class.
*
* @var array
*/
private static $_accessibleProperties = [];

/**
* See class description.
*
Expand Down Expand Up @@ -135,24 +126,15 @@ protected function handlePropertyMagicMethod($property, $type, array $arguments)
*/
private function isPropertyAccessible($propertyName)
{
$result = false;
$className = get_class($this);
$reflectionService = ReflectionService::get();

if (false === isset(self::$_accessibleProperties[$className])) {
self::$_accessibleProperties[$className] = [];

/** @var ClassReflection $classReflection */
$classReflection = GeneralUtility::makeInstance(ClassReflection::class, $this);
$properties = $classReflection->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED);

foreach ($properties as $property) {
if (false === $property->isTaggedWith('disableMagicMethods')) {
self::$_accessibleProperties[$className][$property->getName()] = true;
}
}

unset($classReflection);
if ($reflectionService->isClassPropertyAccessible($className, $propertyName)) {
$property = $reflectionService->getClassAccessibleProperty($className, $propertyName);
$result = false === $property->isTaggedWith('disableMagicMethods');
}

return isset(self::$_accessibleProperties[$className][$propertyName]);
return $result;
}
}
11 changes: 11 additions & 0 deletions Tests/Fixture/Reflection/ExampleReflection.php
@@ -0,0 +1,11 @@
<?php
namespace Romm\ConfigurationObject\Tests\Fixture\Reflection;

class ExampleReflection
{
public $foo;

protected $bar;

private $baz;
}
96 changes: 96 additions & 0 deletions Tests/Unit/Core/Service/ReflectionServiceTest.php
@@ -0,0 +1,96 @@
<?php
namespace Romm\ConfigurationObject\Tests\Unit\Core\Service\Cache;

use Romm\ConfigurationObject\Core\Service\ReflectionService;
use Romm\ConfigurationObject\Exceptions\PropertyNotAccessibleException;
use Romm\ConfigurationObject\Tests\Fixture\Reflection\ExampleReflection;
use Romm\ConfigurationObject\Tests\Unit\AbstractUnitTest;
use TYPO3\CMS\Extbase\Reflection\ClassReflection;

class ReflectionServiceTest extends AbstractUnitTest
{
/**
* @test
*/
public function classReflectionIsInstantiatedOnlyOnce()
{
$service = new ReflectionService;
$classReflection = $service->getClassReflection(\stdClass::class);
$classReflectionBis = $service->getClassReflection(\stdClass::class);
$classReflectionTer = $service->getClassReflection(self::class);
$this->assertSame($classReflection, $classReflectionBis);
$this->assertNotSame($classReflection, $classReflectionTer);
}

/**
* @test
*/
public function accessiblePropertiesAreAccessed()
{
$service = new ReflectionService;
$accessibleProperties = $service->getAccessibleProperties(ExampleReflection::class);
$this->assertEquals(
['foo', 'bar'],
array_keys($accessibleProperties)
);
}

/**
* @test
*/
public function accessiblePropertiesAreCalculatedOnce()
{
/** @var ReflectionService|\PHPUnit_Framework_MockObject_MockObject $service */
$service = $this->getMockBuilder(ReflectionService::class)
->setMethods(['getClassReflection'])
->getMock();

$classReflection = new ClassReflection(ExampleReflection::class);

$service->expects($this->once())
->method('getClassReflection')
->willReturn($classReflection);

$service->getAccessibleProperties(ExampleReflection::class);
$service->getAccessibleProperties(ExampleReflection::class);
$service->getAccessibleProperties(ExampleReflection::class);
}

/**
* @test
*/
public function accessibleProperty()
{
$service = new ReflectionService;

$this->assertTrue($service->isClassPropertyAccessible(ExampleReflection::class, 'foo'));
$this->assertTrue($service->isClassPropertyAccessible(ExampleReflection::class, 'bar'));
$this->assertFalse($service->isClassPropertyAccessible(ExampleReflection::class, 'baz'));
}

/**
* @test
*/
public function singlePropertyReflectionAreReturned()
{
$service = new ReflectionService;

$fooReflection = $service->getClassAccessibleProperty(ExampleReflection::class, 'foo');
$this->assertEquals('foo', $fooReflection->getName());

$barReflection = $service->getClassAccessibleProperty(ExampleReflection::class, 'bar');
$this->assertEquals('bar', $barReflection->getName());
}

/**
* @test
*/
public function notAccessiblePropertyThrowsException()
{
$this->setExpectedException(PropertyNotAccessibleException::class);

$service = new ReflectionService;

$service->getClassAccessibleProperty(ExampleReflection::class, 'baz');
}
}

0 comments on commit 3e8fd0a

Please sign in to comment.