From 61889d8bd4af83b747a148ae070effeb84e6cd9b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sun, 15 Dec 2024 17:48:28 +0800 Subject: [PATCH] [7.x] Add `Orchestra\Testbench\Support\FluentDecorator` (#280) * [7.x] Add `Orchestra\Testbench\Support\FluentDecorator` Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- src/Foundation/Config.php | 43 +++++-- src/Support/FluentDecorator.php | 169 ++++++++++++++++++++++++++ tests/Support/FluentDecoratorTest.php | 56 +++++++++ 3 files changed, 256 insertions(+), 12 deletions(-) create mode 100644 src/Support/FluentDecorator.php create mode 100644 tests/Support/FluentDecoratorTest.php diff --git a/src/Foundation/Config.php b/src/Foundation/Config.php index f9c4da91..19be9df7 100644 --- a/src/Foundation/Config.php +++ b/src/Foundation/Config.php @@ -3,9 +3,9 @@ namespace Orchestra\Testbench\Foundation; use Illuminate\Support\Arr; -use Illuminate\Support\Fluent; use Illuminate\Support\LazyCollection; use Orchestra\Testbench\Contracts\Config as ConfigContract; +use Orchestra\Testbench\Support\FluentDecorator; use Symfony\Component\Yaml\Yaml; use function Orchestra\Testbench\join_paths; @@ -100,7 +100,7 @@ * workbench?: TOptionalWorkbenchConfig|null * } */ -class Config extends Fluent implements ConfigContract +class Config extends FluentDecorator implements ConfigContract { /** * All of the attributes set on the fluent instance. @@ -109,7 +109,7 @@ class Config extends Fluent implements ConfigContract * * @phpstan-var TConfig */ - protected $attributes = [ + protected $defaultAttributes = [ 'laravel' => null, 'env' => [], 'providers' => [], @@ -185,6 +185,18 @@ class Config extends Fluent implements ConfigContract */ protected static $cachedConfiguration; + /** + * Construct a new Config instance. + * + * @param iterable $attributes + * + * @phpstan-param TOptionalConfig $attributes + */ + public function __construct($attributes = []) + { + parent::__construct(array_replace($this->defaultAttributes, $attributes)); + } + /** * Load configuration from Yaml file. * @@ -256,7 +268,7 @@ public static function cacheFromYaml(string $workingPath, ?string $filename = 't */ public function addProviders(array $providers) { - $this->attributes['providers'] = array_unique(array_merge($this->attributes['providers'], $providers)); + $this->fluent['providers'] = array_unique(array_merge($this->fluent['providers'], $providers)); return $this; } @@ -270,11 +282,13 @@ public function addProviders(array $providers) */ public function getExtraAttributes(): array { + $attributes = $this->fluent->getAttributes(); + return [ - 'env' => Arr::get($this->attributes, 'env', []), - 'bootstrappers' => Arr::get($this->attributes, 'bootstrappers', []), - 'providers' => Arr::get($this->attributes, 'providers', []), - 'dont-discover' => Arr::get($this->attributes, 'dont-discover', []), + 'env' => Arr::get($attributes, 'env', []), + 'bootstrappers' => Arr::get($attributes, 'bootstrappers', []), + 'providers' => Arr::get($attributes, 'providers', []), + 'dont-discover' => Arr::get($attributes, 'dont-discover', []), ]; } @@ -287,10 +301,13 @@ public function getExtraAttributes(): array */ public function getPurgeAttributes(): array { - return array_merge( + $config = array_merge( $this->purgeConfig, - $this->attributes['purge'], + $this->fluent['purge'], ); + + /** @var TPurgeConfig $config */ + return $config; } /** @@ -302,14 +319,16 @@ public function getPurgeAttributes(): array */ public function getWorkbenchAttributes(): array { + $attributes = $this->fluent->getAttributes(); + $config = array_merge( $this->workbenchConfig, - $this->attributes['workbench'], + $attributes['workbench'], ); $config['discovers'] = array_merge( $this->workbenchDiscoversConfig, - Arr::get($this->attributes, 'workbench.discovers', []) + Arr::get($attributes, 'workbench.discovers', []) ); /** @var TWorkbenchConfig $config */ diff --git a/src/Support/FluentDecorator.php b/src/Support/FluentDecorator.php new file mode 100644 index 00000000..15d57660 --- /dev/null +++ b/src/Support/FluentDecorator.php @@ -0,0 +1,169 @@ + + * @implements \ArrayAccess + */ +abstract class FluentDecorator implements Arrayable, ArrayAccess, Jsonable, JsonSerializable +{ + use ForwardsCalls; + + /** + * The Fluent instance. + * + * @var \Illuminate\Support\Fluent + */ + protected Fluent $fluent; + + /** + * Create a new fluent instance. + * + * @param iterable $attributes + */ + public function __construct($attributes = []) + { + $this->fluent = new Fluent($attributes); + } + + /** + * Convert the fluent instance to an array. + * + * @return array + */ + public function toArray() + { + return $this->fluent->getAttributes(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * Convert the fluent instance to JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return (string) json_encode($this->jsonSerialize(), $options); + } + + /** + * Determine if the given offset exists. + * + * @param TKey $offset + */ + public function offsetExists($offset): bool + { + return $this->fluent->offsetExists($offset); + } + + /** + * Get the value for a given offset. + * + * @param TKey $offset + * @return TValue|null + */ + public function offsetGet($offset): mixed + { + return $this->fluent->offsetGet($offset); + } + + /** + * Set the value at the given offset. + * + * @param TKey $offset + * @param TValue $value + */ + public function offsetSet($offset, $value): void + { + $this->fluent->offsetSet($offset, $value); + } + + /** + * Unset the value at the given offset. + * + * @param TKey $offset + */ + public function offsetUnset($offset): void + { + $this->fluent->offsetUnset($offset); + } + + /** + * Handle dynamic calls to the fluent instance to set attributes. + * + * @param TKey $method + * @param array{0: ?TValue} $parameters + * @return $this + */ + public function __call($method, $parameters) + { + return $this->forwardDecoratedCallTo($this->fluent, $method, $parameters); + } + + /** + * Dynamically retrieve the value of an attribute. + * + * @param TKey $key + * @return TValue|null + */ + public function __get($key) + { + return $this->fluent->get($key); + } + + /** + * Dynamically set the value of an attribute. + * + * @param TKey $key + * @param TValue $value + * @return void + */ + public function __set($key, $value) + { + $this->fluent->offsetSet($key, $value); + } + + /** + * Dynamically check if an attribute is set. + * + * @param TKey $key + * @return bool + */ + public function __isset($key) + { + return $this->fluent->offsetExists($key); + } + + /** + * Dynamically unset an attribute. + * + * @param TKey $key + * @return void + */ + public function __unset($key) + { + $this->fluent->offsetUnset($key); + } +} diff --git a/tests/Support/FluentDecoratorTest.php b/tests/Support/FluentDecoratorTest.php new file mode 100644 index 00000000..987cac73 --- /dev/null +++ b/tests/Support/FluentDecoratorTest.php @@ -0,0 +1,56 @@ + true, 'class' => __CLASS__]) extends FluentDecorator + { + // ... + }; + + $this->assertTrue(isset($fluent['testbench'])); + $this->assertTrue(isset($fluent['class'])); + $this->assertFalse(isset($fluent['workbench'])); + + $this->assertTrue($fluent['testbench']); + $this->assertNull($fluent['workbench']); + + $this->assertSame($attributes, $fluent->getAttributes()); + $this->assertSame($attributes, $fluent->toArray()); + $this->assertSame(json_encode($attributes), $fluent->toJson()); + $this->assertSame($attributes, $fluent->jsonSerialize()); + + $this->assertFalse(isset($fluent['laravel'])); + $this->assertNull($fluent['laravel']); + + $this->assertInstanceOf(FluentDecorator::class, $fluent->laravel(Application::VERSION)); + + $this->assertTrue(isset($fluent['laravel'])); + $this->assertSame(Application::VERSION, $fluent['laravel']); + + unset($fluent['class']); + + $this->assertFalse(isset($fluent['class'])); + $this->assertFalse(isset($fluent->class)); + $this->assertNull($fluent['class']); + $this->assertNull($fluent->class); + + $this->assertFalse(isset($fluent->file)); + + $fluent->file = __FILE__; + + $this->assertTrue(isset($fluent->file)); + + unset($fluent->file); + + $this->assertFalse(isset($fluent->file)); + } +}