Skip to content

Commit

Permalink
Allow decorating anonymous classes; Allow direct access to public var…
Browse files Browse the repository at this point in the history
…iables
  • Loading branch information
Coil committed Dec 30, 2021
1 parent a1b8094 commit 0e74e2b
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 11 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "c01l/phpdecorator",
"version": "1.1.0",
"version": "1.2.0",
"description": "The phpdecorator library can be used to wrap functions of objects and classes with additional functionality. This is a feature that can be compared to Python decorators. ",
"type": "library",
"license": "MIT",
Expand Down
32 changes: 22 additions & 10 deletions src/DecoratorManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ public function decorate(object $real): object
throw new DecoratorException("Cannot decorate final class: " . $real::class);
}

$trait = "\\" . DecoratorHelperTrait::class;

$wrappers = [];
$methods = $rc->getMethods();
$overwrite_methods = "";
Expand All @@ -64,7 +62,7 @@ public function decorate(object $real): object
"' . $method->name . '"
);';
} else {
$bodyFn = fn($method) => 'return parent::' . $method->name . '(...get_defined_vars());';
$bodyFn = fn($method) => 'return $this->real->' . $method->name . '(...get_defined_vars());';
}

$overwrite_methods .= $this->handleMethod(
Expand All @@ -78,12 +76,20 @@ public function decorate(object $real): object
return $real;
}

$classBody = ' { use ' . $trait . '; public function __construct(private mixed $real) {} '

$trait = "\\" . DecoratorHelperTrait::class;
$trait2 = "\\" . ProxyVariableAccessTrait::class;
$classBody = ' { use ' . $trait . '; use ' . $trait2 . '; public function __construct(private mixed $real) {} '
. $overwrite_methods . '};';

$this->storeInCache($real::class, $classBody);

$classDef = 'return new class($real) extends \\' . $rc->getName() . $classBody;
$extends = "";
if (!$rc->isAnonymous()) {
$extends = "extends \\{$rc->getName()} ";
}

$classDef = 'return new class($real) ' . $extends . $classBody;
$obj = eval($classDef);
$obj->setWrappers($wrappers);
return $obj;
Expand Down Expand Up @@ -148,15 +154,20 @@ function (...$args) { return parent::' . $method->name . '(...$args);},
$classBody = '{ use ' . $trait . '; ' . $overwrite_methods . '};';
$this->storeInCache($className, $classBody);

$classDef = "return new class extends \\{$rc->getName()} $classBody";
$extends = "";
if (!$rc->isAnonymous()) {
$extends = "extends \\{$rc->getName()}";
}

$classDef = "return new class $extends $classBody";
$obj = eval($classDef);
$obj->setWrappers($wrappers);
return $obj;
}

private function classToFilename(string $class): string
{
return str_replace("\\", "_", $class);
return preg_replace("/[^[:alnum:]]/", "_", $class);
}

private function loadFromCache(string $class): string|false
Expand Down Expand Up @@ -222,10 +233,11 @@ private function buildFunctionHead(ReflectionMethod $method): string
{
$modifiers = implode(" ", Reflection::getModifierNames($method->getModifiers()));
$paramList = implode(", ", array_map([$this, "buildFunctionParam"], $method->getParameters()));
$type = $method->getReturnType()->getName();
$type = $method->getReturnType()?->getName();
$type = $type ? ": $type" : "";

$attrs = $this->buildAttributeList($method->getAttributes());
return "$attrs $modifiers function " . $method->name . "($paramList): $type";
return "$attrs $modifiers function " . $method->name . "($paramList) $type";
}

private function buildFunctionParam(ReflectionParameter $parameter): string
Expand All @@ -240,7 +252,7 @@ private function buildFunctionParam(ReflectionParameter $parameter): string
}

$attrs = $this->buildAttributeList($parameter->getAttributes());
return "$attrs {$parameter->getType()->getName()} \${$parameter->getName()}$default";
return "$attrs {$parameter->getType()?->getName()} \${$parameter->getName()}$default";
}

private function buildAttributeList(array $attrs): string
Expand Down
21 changes: 21 additions & 0 deletions src/ProxyVariableAccessTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace C01l\PhpDecorator;

trait ProxyVariableAccessTrait
{
public function __set(string $name, $value): void
{
$this->real->$name = $value;
}

public function __get(string $name)
{
return $this->real->$name;
}

public function __isset(string $name): bool
{
return isset($this->real->$name);
}
}
35 changes: 35 additions & 0 deletions tests/ObjectDecoratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use C01l\PhpDecorator\DecoratorManager;
use C01l\PhpDecorator\Tests\TestClasses\FinalClass;
use C01l\PhpDecorator\Tests\TestClasses\Logger;
use C01l\PhpDecorator\Tests\TestClasses\LoggingDecorator;
use C01l\PhpDecorator\Tests\TestClasses\MultipleDecoratorClass;
use C01l\PhpDecorator\Tests\TestClasses\SimpleContainer;
use C01l\PhpDecorator\Tests\TestClasses\SimpleMethodsClass;
Expand Down Expand Up @@ -78,4 +79,38 @@ public function testKeepsUnrelatedAttributes()
$attrNames = array_map(fn($a) => $a->getName(), $attrs);
$this->assertEquals([UnrelatedAttribute::class], $attrNames);
}

public function testDecorateAnonymousClass()
{
$obj = new class {
#[LoggingDecorator]
public function test($x)
{
return $x;
}
};
$obj = $this->sut->decorate($obj);
$this->assertEquals(2, $obj->test(2));
}

public function testAccessingPublicVariables()
{
$obj = new class {
public int $i = 0;

public function get(): int
{
return $this->i;
}

#[LoggingDecorator]
public function test()
{
}
};
$obj = $this->sut->decorate($obj);
$obj->i = 3;
$this->assertEquals(3, $obj->get());
$this->assertEquals(3, $obj->i);
}
}

0 comments on commit 0e74e2b

Please sign in to comment.