jasny/reflection-factory

Abstract factory for PHP Reflection

v1.1.2 2022-06-10 23:23 UTC

This package is auto-updated.

Last update: 2024-07-11 03:59:15 UTC


README

PHP Scrutinizer Code Quality Code Coverage Packagist Stable Version Packagist License

Factory to use in dependency injection when using PHP Reflection.

Why use a factory instead or just doing new ReflectionClass()?

Dependency injection might seem like making things needlessly complex. However it's a key component in building maintainable (and testable) code.

Using new within your classes creates a strong coupling between classes. This makes it more difficult when writing unit tests because there is no opertunity to mock the reflection. In practice this means that a class using ReflectionClass can only be tested with real, existing classes.

Using ReflectionFactory with dependency injection, allows you to inject a mock of the factory instead. This in term allows you to create mock Reflection objects, for non-existing classes, functions, properties, etc.

Installation

composer require jasny/reflection-factory

Usage

use Jasny\ReflectionFactory\ReflectionFactory;

$factory = new ReflectionFactory();
$reflection = $factory->reflectClass(\DateTime::class);

Example use case

Without dependency injection

use Jasny\ReflectionFactory\ReflectionFactory;

class SomeTool
{
    public function foo(string $class)
    {
        $reflection = new ReflectionClass($class);
        
        return $reflection->getConstant('FOO');
    }
}

But writing the test is hard, as it doesn't allow mocking. Instead we need to create a SomeToolTestFooSupport class just to test this feature.

class SomeToolTestFooSupport
{
    const FOO = 10;
}

In the unit test we do

use PHPUnit\Framework\TestCase;

class SomeToolTest extends TestCase
{
    public function testFoo()
    {
        $tool = new SomeTool();
        
        $this->assertEquals(10, $tool->foo("SomeToolTestFooSupport"));
    }
}

Adding one test class isn't so bad. But consider we need to add one per test, it quickly becomes a mess.

With dependency injection

Dependency injection adds a little overhead to the class as we need to pass the reflection factory to SomeTool.

use Jasny\ReflectionFactory\ReflectionFactoryInterface;

class SomeTool
{
    protected $reflectionFactory;
    
    public function __construct(ReflectionFactoryInterface $reflectionFactory)
    {
        $this->reflectionFactory = $reflectionFactory;    
    }
    
    public function foo(string $class)
    {
        return $this->reflectionFactory->reflectClass($class)->getConstant('FOO');
    }
}

In the unit test, we mock the ReflectionClass and ReflectionFactory. The tests class FakeClass doesn't need to exist.

use PHPUnit\Framework\TestCase;
use Jasny\ReflectionFactory\ReflectionFactoryInterface;

class SomeToolTest extends TestCase
{
    public function testFoo()
    {
        $mockReflection = $this->createMock(\ReflectionClass::class);
        $mockReflection->expects($this->once())->method('getConstant')
            ->with('FOO')->willReturn(10);
            
        $mockFactory = $this->createMock(ReflectionFactoryInterface::class);
        $mockFactory->expects($this->once())->method('reflectClass')
            ->with('FakeClass')->willReturn($mockReflection);
            
        $tool = new SomeTool($mockFactory);
        
        $this->assertEquals(10, $tool->foo("FakeClass"));
    }
}

Methods

Some PHP functions have been wrapped, so they can be mocked