From ceb1af6c12a6ec637a553d0e6db0d434d0fd6bd9 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Fri, 14 May 2021 14:10:11 +0100 Subject: [PATCH 1/3] Ported tests from wip/namespace-alias-support-extendable-trait --- src/Support/Testing/MocksClassLoader.php | 55 +++++++++++++ tests/Extension/ExtendableTest.php | 99 ++++++++++++++++++------ 2 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 src/Support/Testing/MocksClassLoader.php diff --git a/src/Support/Testing/MocksClassLoader.php b/src/Support/Testing/MocksClassLoader.php new file mode 100644 index 00000000..4ba9f2fc --- /dev/null +++ b/src/Support/Testing/MocksClassLoader.php @@ -0,0 +1,55 @@ +classLoader = new ClassLoader( + new Filesystem(), + $basePath, + $manifestPath + ); + + $this->classLoader->register(); + } + + /** + * Inits a class and binds the ClassLoader to it, used by a class that uses the `Extendable` trait. + * + * The mock will use the class loader from this trait. + * + * @param string $class + * @return mixed + */ + protected function extendableMakeClassWithClassLoader($class) + { + $subject = new $class(); + $subject->extendableRegisterClassLoader($this->classLoader); + return $subject; + } +} diff --git a/tests/Extension/ExtendableTest.php b/tests/Extension/ExtendableTest.php index dfbe9322..9a283cf2 100644 --- a/tests/Extension/ExtendableTest.php +++ b/tests/Extension/ExtendableTest.php @@ -2,32 +2,53 @@ use Winter\Storm\Extension\Extendable; use Winter\Storm\Extension\ExtensionBase; +use Winter\Storm\Filesystem\Filesystem; +use Winter\Storm\Support\ClassLoader; +use Winter\Storm\Support\Testing\MocksClassLoader; class ExtendableTest extends TestCase { + use MocksClassLoader; + + public function setUp(): void + { + parent::setUp(); + + $this->registerMockClassLoader(); + + $this->classLoader->addDirectories([ + 'plugins' + ]); + + $this->classLoader->addNamespaceAliases([ + 'Real\\ExtendableTest' => 'Alias\\ExtendableTest', + 'Real' => 'Alias', + ]); + } + public function testExtendingExtendableClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $this->assertNull($subject->classAttribute); ExtendableTestExampleExtendableClass::extend(function ($extension) { $extension->classAttribute = 'bar'; }); - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $this->assertEquals('bar', $subject->classAttribute); } public function testSettingDeclaredPropertyOnClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->classAttribute = 'Test'; $this->assertEquals('Test', $subject->classAttribute); } public function testSettingUndeclaredPropertyOnClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->newAttribute = 'Test'; $this->assertNull($subject->newAttribute); $this->assertFalse(property_exists($subject, 'newAttribute')); @@ -35,7 +56,7 @@ public function testSettingUndeclaredPropertyOnClass() public function testSettingDeclaredPropertyOnBehavior() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $behavior = $subject->getClassExtension('ExtendableTestExampleBehaviorClass1'); $subject->behaviorAttribute = 'Test'; @@ -46,7 +67,7 @@ public function testSettingDeclaredPropertyOnBehavior() public function testDynamicPropertyOnClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $this->assertFalse(property_exists($subject, 'newAttribute')); $subject->addDynamicProperty('dynamicAttribute', 'Test'); $this->assertEquals('Test', $subject->dynamicAttribute); @@ -55,7 +76,7 @@ public function testDynamicPropertyOnClass() public function testDynamicallyExtendingClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->extendClassWith('ExtendableTestExampleBehaviorClass2'); $this->assertTrue($subject->isClassExtendedWith('ExtendableTestExampleBehaviorClass1')); @@ -64,7 +85,7 @@ public function testDynamicallyExtendingClass() public function testDynamicMethodOnClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getFooAnotherWay', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); $this->assertEquals('foo', $subject->getFoo()); @@ -73,7 +94,7 @@ public function testDynamicMethodOnClass() public function testDynamicExtendAndMethodOnClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->extendClassWith('ExtendableTestExampleBehaviorClass2'); $subject->addDynamicMethod('getOriginalFoo', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); @@ -83,9 +104,18 @@ public function testDynamicExtendAndMethodOnClass() $this->assertEquals('foo', $subject->getOriginalFoo()); } + public function testExtendOnClassWithClassLoaderAliases() + { + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClassAlias1::class); + $this->assertTrue($subject->isClassExtendedWith('Real.ExtendableTest.ExampleBehaviorClass1')); + + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClassAlias2::class); + $this->assertTrue($subject->isClassExtendedWith('Real.ExampleBehaviorClass1')); + } + public function testDynamicClosureOnClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('sayHello', function () { return 'Hello world'; }); @@ -95,7 +125,7 @@ public function testDynamicClosureOnClass() public function testDynamicCallableOnClass() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getAppName', ['ExtendableTestExampleClass', 'getName']); $this->assertEquals('winter', $subject->getAppName()); @@ -121,7 +151,7 @@ public function testCallingUndefinedStaticMethod() public function testAccessingProtectedProperty() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $this->assertEmpty($subject->protectedFoo); $subject->protectedFoo = 'snickers'; @@ -130,10 +160,11 @@ public function testAccessingProtectedProperty() public function testAccessingProtectedMethod() { + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessage('Call to undefined method ExtendableTestExampleExtendableClass::protectedBar()'); + $this->expectExceptionMessage('Call to undefined method ' . get_class($subject) . '::protectedBar()'); - $subject = new ExtendableTestExampleExtendableClass; echo $subject->protectedBar(); } @@ -149,27 +180,27 @@ public function testInvalidImplementValue() { $this->expectException(Exception::class); $this->expectExceptionMessage('Class ExtendableTestInvalidExtendableClass contains an invalid $implement value'); - + $result = new ExtendableTestInvalidExtendableClass; } public function testSoftImplementFake() { - $result = new ExtendableTestExampleExtendableSoftImplementFakeClass; + $result = $this->mockClassLoader(ExtendableTestExampleExtendableSoftImplementFakeClass::class); $this->assertFalse($result->isClassExtendedWith('RabbleRabbleRabble')); $this->assertEquals('working', $result->getStatus()); } public function testSoftImplementReal() { - $result = new ExtendableTestExampleExtendableSoftImplementRealClass; + $result = $this->mockClassLoader(ExtendableTestExampleExtendableSoftImplementRealClass::class); $this->assertTrue($result->isClassExtendedWith('ExtendableTestExampleBehaviorClass1')); $this->assertEquals('foo', $result->getFoo()); } public function testSoftImplementCombo() { - $result = new ExtendableTestExampleExtendableSoftImplementComboClass; + $result = $this->mockClassLoader(ExtendableTestExampleExtendableSoftImplementComboClass::class); $this->assertFalse($result->isClassExtendedWith('RabbleRabbleRabble')); $this->assertTrue($result->isClassExtendedWith('ExtendableTestExampleBehaviorClass1')); $this->assertTrue($result->isClassExtendedWith('ExtendableTestExampleBehaviorClass2')); @@ -178,7 +209,7 @@ public function testSoftImplementCombo() public function testDotNotation() { - $subject = new ExtendableTestExampleExtendableClassDotNotation(); + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClassDotNotation::class); $subject->extendClassWith('ExtendableTest.ExampleBehaviorClass2'); $this->assertTrue($subject->isClassExtendedWith('ExtendableTest.ExampleBehaviorClass1')); @@ -187,19 +218,19 @@ public function testDotNotation() public function testMethodExists() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $this->assertTrue($subject->methodExists('extend')); } public function testMethodNotExists() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $this->assertFalse($subject->methodExists('missingFunction')); } public function testDynamicMethodExists() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getFooAnotherWay', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); $this->assertTrue($subject->methodExists('getFooAnotherWay')); @@ -207,7 +238,7 @@ public function testDynamicMethodExists() public function testGetClassMethods() { - $subject = new ExtendableTestExampleExtendableClass; + $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getFooAnotherWay', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); $methods = $subject->getClassMethods(); @@ -297,6 +328,22 @@ public function getProtectedFooAttribute() } } +/* + * Example class that has extensions enabled via `ClassLoader` alias + */ +class ExtendableTestExampleExtendableClassAlias1 extends Extendable +{ + public $implement = ['Alias.ExtendableTest.ExampleBehaviorClass1']; +} + +/* + * Example class that has extensions enabled via `ClassLoader` alias + */ +class ExtendableTestExampleExtendableClassAlias2 extends Extendable +{ + public $implement = ['Alias.ExampleBehaviorClass1']; +} + /** * A normal class without extensions enabled */ @@ -378,3 +425,9 @@ public function getProtectedFooAttribute() */ class_alias('ExtendableTestExampleBehaviorClass1', 'ExtendableTest\\ExampleBehaviorClass1'); class_alias('ExtendableTestExampleBehaviorClass2', 'ExtendableTest\\ExampleBehaviorClass2'); + +/* + * Add namespaces for `ClassLoader` aliasing + */ +class_alias('ExtendableTestExampleBehaviorClass1', 'Real\\ExtendableTest\\ExampleBehaviorClass1'); +class_alias('ExtendableTestExampleBehaviorClass1', 'Real\\ExampleBehaviorClass1'); From 44413fa435a5381c386f62d0f3b670c515c5bda8 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Fri, 14 May 2021 14:12:08 +0100 Subject: [PATCH 2/3] Added reverse alias support in ExtendedTrait --- src/Extension/ExtendableTrait.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index fa07e73a..988c9239 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -1,9 +1,11 @@ [] ]; + protected $extendableClassLoader = null; + /** * @var array Used to extend the constructor of an extendable class. Eg: * @@ -93,6 +97,21 @@ public function extendableConstruct() $this->extendClassWith($useClass); } + + // If App is available, bind the ClassLoader + if (class_exists('App')) { + $this->extendableClassLoader = App::make(ClassLoader::class); + } + } + + /** + * Bind ClassLoader to this object + * @param ClassLoader $classLoader + * @return void + */ + public function extendableRegisterClassLoader(ClassLoader $classLoader): void + { + $this->extendableClassLoader = $classLoader; } /** @@ -204,7 +223,7 @@ public function extendClassWith($extensionName) $extensionName = str_replace('.', '\\', trim($extensionName)); - if (isset($this->extensionData['extensions'][$extensionName])) { + if ($this->isClassExtendedWith($extensionName)) { throw new Exception(sprintf( 'Class %s has already been extended with %s', get_class($this), @@ -225,6 +244,10 @@ public function extendClassWith($extensionName) public function isClassExtendedWith($name) { $name = str_replace('.', '\\', trim($name)); + if ($this->extendableClassLoader && $alias = $this->extendableClassLoader->getReverseAlias($name)) { + return isset($this->extensionData['extensions'][$name]) + || isset($this->extensionData['extensions'][$alias]); + } return isset($this->extensionData['extensions'][$name]); } From 888568c9d7213c1044dcb62f2cef45ee95f971cc Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Fri, 14 May 2021 15:32:53 +0100 Subject: [PATCH 3/3] Renamed method and updated test --- src/Support/Testing/MocksClassLoader.php | 2 +- tests/Extension/ExtendableTest.php | 46 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Support/Testing/MocksClassLoader.php b/src/Support/Testing/MocksClassLoader.php index 4ba9f2fc..17727ed1 100644 --- a/src/Support/Testing/MocksClassLoader.php +++ b/src/Support/Testing/MocksClassLoader.php @@ -46,7 +46,7 @@ protected function registerMockClassLoader($basePath = null, $manifestPath = nul * @param string $class * @return mixed */ - protected function extendableMakeClassWithClassLoader($class) + protected function extendableMakeClass($class) { $subject = new $class(); $subject->extendableRegisterClassLoader($this->classLoader); diff --git a/tests/Extension/ExtendableTest.php b/tests/Extension/ExtendableTest.php index 9a283cf2..d3e52d26 100644 --- a/tests/Extension/ExtendableTest.php +++ b/tests/Extension/ExtendableTest.php @@ -28,27 +28,27 @@ public function setUp(): void public function testExtendingExtendableClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $this->assertNull($subject->classAttribute); ExtendableTestExampleExtendableClass::extend(function ($extension) { $extension->classAttribute = 'bar'; }); - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $this->assertEquals('bar', $subject->classAttribute); } public function testSettingDeclaredPropertyOnClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->classAttribute = 'Test'; $this->assertEquals('Test', $subject->classAttribute); } public function testSettingUndeclaredPropertyOnClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->newAttribute = 'Test'; $this->assertNull($subject->newAttribute); $this->assertFalse(property_exists($subject, 'newAttribute')); @@ -56,7 +56,7 @@ public function testSettingUndeclaredPropertyOnClass() public function testSettingDeclaredPropertyOnBehavior() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $behavior = $subject->getClassExtension('ExtendableTestExampleBehaviorClass1'); $subject->behaviorAttribute = 'Test'; @@ -67,7 +67,7 @@ public function testSettingDeclaredPropertyOnBehavior() public function testDynamicPropertyOnClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $this->assertFalse(property_exists($subject, 'newAttribute')); $subject->addDynamicProperty('dynamicAttribute', 'Test'); $this->assertEquals('Test', $subject->dynamicAttribute); @@ -76,7 +76,7 @@ public function testDynamicPropertyOnClass() public function testDynamicallyExtendingClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->extendClassWith('ExtendableTestExampleBehaviorClass2'); $this->assertTrue($subject->isClassExtendedWith('ExtendableTestExampleBehaviorClass1')); @@ -85,7 +85,7 @@ public function testDynamicallyExtendingClass() public function testDynamicMethodOnClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getFooAnotherWay', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); $this->assertEquals('foo', $subject->getFoo()); @@ -94,7 +94,7 @@ public function testDynamicMethodOnClass() public function testDynamicExtendAndMethodOnClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->extendClassWith('ExtendableTestExampleBehaviorClass2'); $subject->addDynamicMethod('getOriginalFoo', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); @@ -106,16 +106,16 @@ public function testDynamicExtendAndMethodOnClass() public function testExtendOnClassWithClassLoaderAliases() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClassAlias1::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClassAlias1::class); $this->assertTrue($subject->isClassExtendedWith('Real.ExtendableTest.ExampleBehaviorClass1')); - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClassAlias2::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClassAlias2::class); $this->assertTrue($subject->isClassExtendedWith('Real.ExampleBehaviorClass1')); } public function testDynamicClosureOnClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('sayHello', function () { return 'Hello world'; }); @@ -125,7 +125,7 @@ public function testDynamicClosureOnClass() public function testDynamicCallableOnClass() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getAppName', ['ExtendableTestExampleClass', 'getName']); $this->assertEquals('winter', $subject->getAppName()); @@ -151,7 +151,7 @@ public function testCallingUndefinedStaticMethod() public function testAccessingProtectedProperty() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $this->assertEmpty($subject->protectedFoo); $subject->protectedFoo = 'snickers'; @@ -160,7 +160,7 @@ public function testAccessingProtectedProperty() public function testAccessingProtectedMethod() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $this->expectException(BadMethodCallException::class); $this->expectExceptionMessage('Call to undefined method ' . get_class($subject) . '::protectedBar()'); @@ -186,21 +186,21 @@ public function testInvalidImplementValue() public function testSoftImplementFake() { - $result = $this->mockClassLoader(ExtendableTestExampleExtendableSoftImplementFakeClass::class); + $result = $this->extendableMakeClass(ExtendableTestExampleExtendableSoftImplementFakeClass::class); $this->assertFalse($result->isClassExtendedWith('RabbleRabbleRabble')); $this->assertEquals('working', $result->getStatus()); } public function testSoftImplementReal() { - $result = $this->mockClassLoader(ExtendableTestExampleExtendableSoftImplementRealClass::class); + $result = $this->extendableMakeClass(ExtendableTestExampleExtendableSoftImplementRealClass::class); $this->assertTrue($result->isClassExtendedWith('ExtendableTestExampleBehaviorClass1')); $this->assertEquals('foo', $result->getFoo()); } public function testSoftImplementCombo() { - $result = $this->mockClassLoader(ExtendableTestExampleExtendableSoftImplementComboClass::class); + $result = $this->extendableMakeClass(ExtendableTestExampleExtendableSoftImplementComboClass::class); $this->assertFalse($result->isClassExtendedWith('RabbleRabbleRabble')); $this->assertTrue($result->isClassExtendedWith('ExtendableTestExampleBehaviorClass1')); $this->assertTrue($result->isClassExtendedWith('ExtendableTestExampleBehaviorClass2')); @@ -209,7 +209,7 @@ public function testSoftImplementCombo() public function testDotNotation() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClassDotNotation::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClassDotNotation::class); $subject->extendClassWith('ExtendableTest.ExampleBehaviorClass2'); $this->assertTrue($subject->isClassExtendedWith('ExtendableTest.ExampleBehaviorClass1')); @@ -218,19 +218,19 @@ public function testDotNotation() public function testMethodExists() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $this->assertTrue($subject->methodExists('extend')); } public function testMethodNotExists() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $this->assertFalse($subject->methodExists('missingFunction')); } public function testDynamicMethodExists() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getFooAnotherWay', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); $this->assertTrue($subject->methodExists('getFooAnotherWay')); @@ -238,7 +238,7 @@ public function testDynamicMethodExists() public function testGetClassMethods() { - $subject = $this->mockClassLoader(ExtendableTestExampleExtendableClass::class); + $subject = $this->extendableMakeClass(ExtendableTestExampleExtendableClass::class); $subject->addDynamicMethod('getFooAnotherWay', 'getFoo', 'ExtendableTestExampleBehaviorClass1'); $methods = $subject->getClassMethods();