From 72ad31b547177404622c718d2facf7dea2a1c2e8 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 14 Aug 2023 16:01:20 +0800 Subject: [PATCH 1/2] Remove deprecated "get_parent_class" calls in Extendable trait. This uses Reflection to instead determine the parent class and available magic methods to pass through to. In order to prevent infinite looping (which could've potentially been a problem before), it will also ignore any "extendable" classes when determining the parent. Also fixed some tests that were using undefined class properties, and were also throwing deprecation errors. Replaces https://github.com/wintercms/storm/pull/152 --- src/Extension/ExtendableTrait.php | 90 +++++++++++++++++++++++++--- tests/Database/UpdaterTest.php | 2 + tests/Foundation/ApplicationTest.php | 2 + tests/Html/BlockBuilderTest.php | 2 + tests/Support/EventFakeTest.php | 2 + 5 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index 9c54c4b89..d1ee8712c 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -8,6 +8,7 @@ use Winter\Storm\Support\ClassLoader; use Winter\Storm\Support\Serialization; use Illuminate\Support\Facades\App; +use ReflectionException; /** * This extension trait is used when access to the underlying base class @@ -386,9 +387,9 @@ public function extendableGet($name) } } - $parent = get_parent_class(); - if ($parent !== false && method_exists($parent, '__get')) { - return parent::__get($name); + $parent = $this->extensionGetParentClass(); + if ($parent !== false && $this->extensionMethodExists($parent, '__get')) { + return $this->extensionCallMethod($parent, '__get', [$name]); } return null; @@ -413,9 +414,9 @@ public function extendableSet($name, $value) /* * This targets trait usage in particular */ - $parent = get_parent_class(); - if ($parent !== false && method_exists($parent, '__set')) { - parent::__set($name, $value); + $parent = $this->extensionGetParentClass(); + if ($parent !== false && $this->extensionMethodExists($parent, '__set')) { + return $this->extensionCallMethod($parent, '__set', [$name, $value]); } /* @@ -457,9 +458,9 @@ public function extendableCall($name, $params = null) } } - $parent = get_parent_class(); - if ($parent !== false && method_exists($parent, '__call')) { - return parent::__call($name, $params); + $parent = $this->extensionGetParentClass(); + if ($parent !== false && $this->extensionMethodExists($parent, '__call')) { + return $this->extensionCallMethod($parent, '__call', [$name, $params]); } throw new BadMethodCallException(sprintf( @@ -550,4 +551,75 @@ protected function extensionGetClassLoader(): ?ClassLoader return self::$extendableClassLoader = App::make(ClassLoader::class); } + + /** + * Gets the parent class using reflection. + * + * The parent class must either not be the `Extendable` class, or must not be using the `ExtendableTrait` trait, + * in order to prevent infinite loops. + * + * @return ReflectionClass|false + */ + protected function extensionGetParentClass(object $instance = null) + { + // Shortcut to prevent infinite loops if the class extends Extendable. + if ($this instanceof Extendable) { + return false; + } + + // Find if any parent uses the Extendable trait + if (!is_null($instance)) { + $reflector = $instance; + } else { + $reflector = new ReflectionClass($this); + } + $parent = $reflector->getParentClass(); + + // If there's no parent, stop here. + if ($parent === false) { + return false; + } + + while (!in_array(ExtendableTrait::class, $parent->getTraitNames())) { + $parent = $parent->getParentClass(); + if ($parent === false) { + break; + } + } + + // If no parent uses the Extendable trait, then return the parent class + if ($parent === false) { + return $reflector->getParentClass(); + } + + // Otherwise, we need to loop through until we find the parent class that doesn't use the Extendable trait + return $this->extensionGetParentClass($parent); + } + + /** + * Determines if the given class reflection contains the given method. + */ + protected function extensionMethodExists(ReflectionClass $class, string $methodName): bool + { + try { + $method = $class->getMethod($methodName); + + if (!$method->isPublic()) { + return false; + } + } catch (ReflectionException $e) { + return false; + } + + return true; + } + + /** + * Calls a method through reflection. + */ + protected function extensionCallMethod(ReflectionClass $class, string $method, array $params) + { + $method = $class->getMethod($method); + return $method->invokeArgs($this, $params); + } } diff --git a/tests/Database/UpdaterTest.php b/tests/Database/UpdaterTest.php index c8e0946e9..ef60d4573 100644 --- a/tests/Database/UpdaterTest.php +++ b/tests/Database/UpdaterTest.php @@ -4,6 +4,8 @@ class UpdaterTest extends TestCase { + protected Updater $updater; + public function setUp(): void { include_once __DIR__.'/../fixtures/database/SampleClass.php'; diff --git a/tests/Foundation/ApplicationTest.php b/tests/Foundation/ApplicationTest.php index 5897fde8a..f138de05a 100644 --- a/tests/Foundation/ApplicationTest.php +++ b/tests/Foundation/ApplicationTest.php @@ -5,6 +5,8 @@ class ApplicationTest extends TestCase { + protected string $basePath; + protected function setUp(): void { // Mock application diff --git a/tests/Html/BlockBuilderTest.php b/tests/Html/BlockBuilderTest.php index bcc0b28d4..a2e1fc6ab 100644 --- a/tests/Html/BlockBuilderTest.php +++ b/tests/Html/BlockBuilderTest.php @@ -4,6 +4,8 @@ class BlockBuilderTest extends TestCase { + protected BlockBuilder $Block; + public function setUp(): void { $this->Block = new BlockBuilder(); diff --git a/tests/Support/EventFakeTest.php b/tests/Support/EventFakeTest.php index f6654e235..5e74b6805 100644 --- a/tests/Support/EventFakeTest.php +++ b/tests/Support/EventFakeTest.php @@ -5,6 +5,8 @@ class EventFakeTest extends TestCase { + protected EventFake $faker; + public function setUp(): void { $this->faker = new EventFake(new Dispatcher); From 6eda8af3bec7fe496c26cebd78428b66090dfc20 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 16 Oct 2023 09:58:51 +0800 Subject: [PATCH 2/2] Fix PHPStan errors --- phpstan-baseline.neon | 20 +++++--------------- src/Database/Model.php | 3 +-- src/Extension/ExtendableTrait.php | 2 +- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 90ef336b2..1611ee26d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -70,6 +70,11 @@ parameters: count: 1 path: src/Database/Model.php + - + message: "#^Static property Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$dispatcher \\(Illuminate\\\\Contracts\\\\Events\\\\Dispatcher\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: src/Database/Model.php + - message: "#^Call to an undefined method Winter\\\\Storm\\\\Database\\\\Model\\:\\:errors\\(\\)\\.$#" count: 1 @@ -710,21 +715,6 @@ parameters: count: 1 path: src/Database/TreeCollection.php - - - message: "#^Winter\\\\Storm\\\\Extension\\\\Extendable\\:\\:extendableCall\\(\\) calls parent\\:\\:__call\\(\\) but Winter\\\\Storm\\\\Extension\\\\Extendable does not extend any class\\.$#" - count: 1 - path: src/Extension/Extendable.php - - - - message: "#^Winter\\\\Storm\\\\Extension\\\\Extendable\\:\\:extendableGet\\(\\) calls parent\\:\\:__get\\(\\) but Winter\\\\Storm\\\\Extension\\\\Extendable does not extend any class\\.$#" - count: 1 - path: src/Extension/Extendable.php - - - - message: "#^Winter\\\\Storm\\\\Extension\\\\Extendable\\:\\:extendableSet\\(\\) calls parent\\:\\:__set\\(\\) but Winter\\\\Storm\\\\Extension\\\\Extendable does not extend any class\\.$#" - count: 1 - path: src/Extension/Extendable.php - - message: "#^Parameter \\#2 \\$data \\(array\\) of method Winter\\\\Storm\\\\Mail\\\\Mailer\\:\\:queue\\(\\) should be compatible with parameter \\$queue \\(string\\|null\\) of method Illuminate\\\\Contracts\\\\Mail\\\\MailQueue\\:\\:queue\\(\\)$#" count: 1 diff --git a/src/Database/Model.php b/src/Database/Model.php index 7e436569c..e96502dfe 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -1,6 +1,6 @@ extensionGetParentClass(); if ($parent !== false && $this->extensionMethodExists($parent, '__set')) { - return $this->extensionCallMethod($parent, '__set', [$name, $value]); + $this->extensionCallMethod($parent, '__set', [$name, $value]); } /*