From c79cb8bbdb8a07853e6d6a32b8b210ae78c549f6 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Thu, 28 Mar 2024 01:18:38 -0400 Subject: [PATCH] Initial autoload test coverage. --- composer.json | 1 + src/Listeners/CacheClearSubscriber.php | 15 +-- src/Support/DomainAutoloader.php | 125 +++++++++--------- tests/Autoload/CommandTest.php | 36 +++++ tests/Autoload/PolicyTest.php | 4 +- tests/Autoload/ProviderTest.php | 32 +++++ tests/Autoload/ServiceProviderTest.php | 1 - tests/TestCase.php | 6 +- .../Invoicing/Commands/InvoiceDeliver.php | 24 ++++ .../Domain/Invoicing/Models/Invoice.php | 11 ++ .../Providers/InvoiceServiceProvider.php | 27 ++++ .../resources/app/Commands/InvoiceSecret.php | 18 +++ 12 files changed, 223 insertions(+), 77 deletions(-) create mode 100644 tests/Autoload/CommandTest.php create mode 100644 tests/Autoload/ProviderTest.php delete mode 100644 tests/Autoload/ServiceProviderTest.php create mode 100644 tests/resources/Domain/Invoicing/Commands/InvoiceDeliver.php create mode 100644 tests/resources/Domain/Invoicing/Providers/InvoiceServiceProvider.php create mode 100644 tests/resources/app/Commands/InvoiceSecret.php diff --git a/composer.json b/composer.json index 7e8b478..ef083df 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "php": "^8.1|^8.2|^8.3", "illuminate/contracts": "^10.25|^11.0", "laravel/prompts": "^0.1.16", + "lorisleiva/lody": "^0.5.0", "spatie/laravel-package-tools": "^1.13.0" }, "require-dev": { diff --git a/src/Listeners/CacheClearSubscriber.php b/src/Listeners/CacheClearSubscriber.php index b5909ad..9267bde 100644 --- a/src/Listeners/CacheClearSubscriber.php +++ b/src/Listeners/CacheClearSubscriber.php @@ -2,8 +2,8 @@ namespace Lunarstorm\LaravelDDD\Listeners; -use ErrorException; use Illuminate\Events\Dispatcher; +use Lunarstorm\LaravelDDD\Support\DomainAutoloader; class CacheClearSubscriber { @@ -13,18 +13,7 @@ public function __construct() public function handle(): void { - $files = glob(base_path(config('ddd.cache_directory').'/ddd-*.php')); - - foreach ($files as $file) { - try { - unlink($file); - } catch (ErrorException $exception) { - if (! str_contains($exception->getMessage(), 'No such file or directory')) { - dump($exception->getMessage()); - throw $exception; - } - } - } + DomainAutoloader::clearCache(); } /** diff --git a/src/Support/DomainAutoloader.php b/src/Support/DomainAutoloader.php index f0ec82b..1d1cb31 100644 --- a/src/Support/DomainAutoloader.php +++ b/src/Support/DomainAutoloader.php @@ -10,11 +10,14 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Gate; +use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; +use Lorisleiva\Lody\Lody; use Lunarstorm\LaravelDDD\Factories\DomainFactory; use Lunarstorm\LaravelDDD\ValueObjects\DomainObject; -use ReflectionClass; +use Symfony\Component\Finder\Finder; use Throwable; class DomainAutoloader @@ -53,65 +56,78 @@ public function autoload(): void } } - protected function registerDomainServiceProviders(bool|string|null $domainPath = null): void + public function registerDomainServiceProviders(bool|string|null $domainPath = null): void { - $domainPath = is_string($domainPath) ? $domainPath : '*/*ServiceProvider.php'; + // $domainPath = is_string($domainPath) ? $domainPath : '*/*ServiceProvider.php'; + + // $serviceProviders = $this->remember('ddd-domain-service-providers', static function () use ($domainPath) { + // return Arr::map( + // glob(base_path(DomainResolver::domainPath() . '/' . $domainPath)), + // (static function ($serviceProvider) { + + // return Path::filePathToNamespace( + // $serviceProvider, + // DomainResolver::domainPath(), + // DomainResolver::domainRootNamespace() + // ); + // }) + // ); + // }); + + $domainPath = app()->basePath(DomainResolver::domainPath()); + + if (! is_dir($domainPath)) { + return; + } $serviceProviders = $this->remember('ddd-domain-service-providers', static function () use ($domainPath) { - return Arr::map( - glob(base_path(DomainResolver::domainPath().'/'.$domainPath)), - (static function ($serviceProvider) { - - return Path::filePathToNamespace( - $serviceProvider, - DomainResolver::domainPath(), - DomainResolver::domainRootNamespace() - ); - }) - ); + $finder = Finder::create()->files()->in($domainPath); + + return Lody::classesFromFinder($finder) + ->isNotAbstract() + ->isInstanceOf(ServiceProvider::class) + ->toArray(); }); $app = app(); + foreach ($serviceProviders as $serviceProvider) { $app->register($serviceProvider); } } - protected function registerDomainCommands(bool|string|null $domainPath = null): void + public function registerDomainCommands(bool|string|null $domainPath = null): void { - $domainPath = is_string($domainPath) ? $domainPath : '*/Commands/*.php'; + // $domainPath = is_string($domainPath) ? $domainPath : '*/Commands/*.php'; + + $domainPath = app()->basePath(DomainResolver::domainPath()); + + if (! is_dir($domainPath)) { + return; + } + $commands = $this->remember('ddd-domain-commands', static function () use ($domainPath) { - $commands = Arr::map( - glob(base_path(DomainResolver::domainPath().'/'.$domainPath)), - static function ($command) { - return Path::filePathToNamespace( - $command, - DomainResolver::domainPath(), - DomainResolver::domainRootNamespace() - ); - } - ); + $finder = Finder::create()->files()->in($domainPath); - // Filter out invalid commands (Abstract classes and classes not extending Illuminate\Console\Command) - return Arr::where($commands, static function ($command) { - if ( - is_subclass_of($command, Command::class) && - ! (new ReflectionClass($command))->isAbstract() - ) { - ConsoleApplication::starting(static function ($artisan) use ($command): void { - $artisan->resolve($command); - }); - } - }); + return Lody::classesFromFinder($finder) + ->isNotAbstract() + ->isInstanceOf(Command::class) + ->toArray(); }); - ConsoleApplication::starting(static function ($artisan) use ($commands): void { - foreach ($commands as $command) { - $artisan->resolve($command); - } + + foreach ($commands as $class) { + $this->registerCommand($class); + } + } + + public function registerCommand($class) + { + ConsoleApplication::starting(function ($artisan) use ($class) { + $artisan->resolve($class); }); } - protected function registerPolicies(bool|string|null $domainPath = null): void + public function registerPolicies(bool|string|null $domainPath = null): void { $domainPath = is_string($domainPath) ? $domainPath : 'Policies\\{model}Policy'; @@ -136,7 +152,7 @@ protected function registerPolicies(bool|string|null $domainPath = null): void }); } - protected function registerFactories(bool|string|null $domainPath = null): void + public function registerFactories(bool|string|null $domainPath = null): void { $domainPath = is_string($domainPath) ? $domainPath : 'Database\\Factories\\{model}Factory'; @@ -155,22 +171,6 @@ protected function registerFactories(bool|string|null $domainPath = null): void }); } - protected function extractDomainAndModelFromModelNamespace(string $modelName): array - { - // Matches \{domain}\\{model} and extracts domain and model - // For example: Domain\Invoicing\Models\Invoice gives ['domain' => 'Invoicing', 'model' => 'Invoice'] - $regex = '/'.DomainResolver::domainRootNamespace().'\\\\(?.+)\\\\'.$this->configValue('namespaces.models').'\\\\(?.+)/'; - - if (preg_match($regex, $modelName, $matches, PREG_OFFSET_CAPTURE, 0)) { - return [ - 'domain' => $matches['domain'][0], - 'model' => $matches['model'][0], - ]; - } - - return []; - } - protected function remember($fileName, $callback) { // The cache is not available during booting, so we need to roll our own file based cache @@ -190,6 +190,13 @@ protected function remember($fileName, $callback) return $data; } + public static function clearCache() + { + $files = glob(base_path(config('ddd.cache_directory').'/ddd-*.php')); + + File::delete($files); + } + protected static function appNamespace() { try { diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php new file mode 100644 index 0000000..fb0aab1 --- /dev/null +++ b/tests/Autoload/CommandTest.php @@ -0,0 +1,36 @@ +setupTestApplication(); + + DomainAutoloader::clearCache(); +}); + +describe('without autoload', function () { + it('does not register the command', function () { + expect(class_exists('Domain\Invoicing\Commands\InvoiceDeliver'))->toBeTrue(); + expect(fn () => Artisan::call('invoice:deliver'))->toThrow(CommandNotFoundException::class); + }); +}); + +describe('with autoload', function () { + beforeEach(function () { + $this->afterApplicationCreated(function () { + (new DomainAutoloader())->autoload(); + }); + }); + + it('registers the command', function () { + expect(class_exists('Domain\Invoicing\Commands\InvoiceDeliver'))->toBeTrue(); + Artisan::call('invoice:deliver'); + expect(Artisan::output())->toContain('Invoice delivered!'); + }); +}); diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 9f1a621..895d17a 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -9,7 +9,9 @@ Config::set('ddd.domain_namespace', 'Domain'); - (new DomainAutoloader())->autoload(); + $this->afterApplicationCreated(function () { + (new DomainAutoloader())->autoload(); + }); }); it('can autoload domain policy', function ($class, $expectedPolicy) { diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php new file mode 100644 index 0000000..e292e4e --- /dev/null +++ b/tests/Autoload/ProviderTest.php @@ -0,0 +1,32 @@ +setupTestApplication(); + + DomainAutoloader::clearCache(); +}); + +describe('without autoload', function () { + it('does not register the provider', function () { + expect(fn () => app('invoicing'))->toThrow(Exception::class); + }); +}); + +describe('with autoload', function () { + beforeEach(function () { + $this->afterApplicationCreated(function () { + (new DomainAutoloader())->autoload(); + }); + }); + + it('registers the provider', function () { + expect(app('invoicing'))->toEqual('invoicing-singleton'); + $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); + }); +}); diff --git a/tests/Autoload/ServiceProviderTest.php b/tests/Autoload/ServiceProviderTest.php deleted file mode 100644 index b3d9bbc..0000000 --- a/tests/Autoload/ServiceProviderTest.php +++ /dev/null @@ -1 +0,0 @@ -cleanFilesAndFolders(); - (new CacheClearSubscriber())->handle(); - $composerFile = base_path('composer.json'); $data = json_decode(file_get_contents($composerFile), true); @@ -78,6 +76,8 @@ protected function cleanFilesAndFolders() File::deleteDirectory(base_path('Custom')); File::deleteDirectory(base_path('src/Domain')); File::deleteDirectory(base_path('src/Domains')); + + DomainAutoloader::clearCache(); } public function setupTestApplication() diff --git a/tests/resources/Domain/Invoicing/Commands/InvoiceDeliver.php b/tests/resources/Domain/Invoicing/Commands/InvoiceDeliver.php new file mode 100644 index 0000000..9abd7c2 --- /dev/null +++ b/tests/resources/Domain/Invoicing/Commands/InvoiceDeliver.php @@ -0,0 +1,24 @@ +info('Invoice delivered!'); + + if ($secret = Invoice::getSecret()) { + $this->line($secret); + + return; + } + } +} diff --git a/tests/resources/Domain/Invoicing/Models/Invoice.php b/tests/resources/Domain/Invoicing/Models/Invoice.php index 66a6068..99d08e1 100644 --- a/tests/resources/Domain/Invoicing/Models/Invoice.php +++ b/tests/resources/Domain/Invoicing/Models/Invoice.php @@ -6,4 +6,15 @@ class Invoice extends DomainModel { + protected static $secret = null; + + public static function setSecret($secret): void + { + self::$secret = $secret; + } + + public static function getSecret(): ?string + { + return self::$secret; + } } diff --git a/tests/resources/Domain/Invoicing/Providers/InvoiceServiceProvider.php b/tests/resources/Domain/Invoicing/Providers/InvoiceServiceProvider.php new file mode 100644 index 0000000..5e257e5 --- /dev/null +++ b/tests/resources/Domain/Invoicing/Providers/InvoiceServiceProvider.php @@ -0,0 +1,27 @@ +app->singleton('invoicing', function (Application $app) { + return 'invoicing-singleton'; + }); + } + + /** + * Bootstrap any application services. + * + * @return void + */ + public function boot() + { + Invoice::setSecret('invoice-secret'); + } +} diff --git a/tests/resources/app/Commands/InvoiceSecret.php b/tests/resources/app/Commands/InvoiceSecret.php new file mode 100644 index 0000000..5798f48 --- /dev/null +++ b/tests/resources/app/Commands/InvoiceSecret.php @@ -0,0 +1,18 @@ +line(Invoice::getSecret() ?? 'Invoice secret not set.'); + } +}