Skip to content

Commit

Permalink
Further refinements:
Browse files Browse the repository at this point in the history
- refactor autoload internals for better testability
- make autoload caching opt-in via ddd:cache, otherwise the initial cache will never be busted
- clean up tests
  • Loading branch information
JasperTey committed Mar 30, 2024
1 parent 89354b5 commit cfb5073
Show file tree
Hide file tree
Showing 40 changed files with 513 additions and 209 deletions.
62 changes: 27 additions & 35 deletions config/ddd.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,41 +63,6 @@
'scope' => 'Scopes',
],

/*
* The folder where the domain cache files will be stored.
*/
'cache_directory' => 'bootstrap/cache',

'autoload' => [
/*
| Autoload service providers from the domain namespace.
| By default, it loads any non-abstract class inside the domain layer extending Illuminate\Support\ServiceProvider.
| For example: Domain/Invoicing/Providers/InvoicingServiceProvider.php or Domain/Invoicing/InvoicingServiceProvider.php
*/
'providers' => true,

/*
| Autoload commands from the domain namespace.
| By default, it loads any non-abstract class inside the domain layer extending Illuminate\Console\Command.
| For example: Domain/Invoicing/Commands/CreateInvoiceCommand.php
*/
'commands' => true,

/*
| Autoload policies from the domain namespace.
| By default, it uses the configured `ddd.namespaces.policy` namespace to guess the policy name.
| For example: Domain/Invoicing/Policies/InvoicePolicy.php
*/
'policies' => true,

/*
| Autoload factories from the domain namespace.
| By default, it uses the configured `ddd.namespaces.factory` namespace to guess the factory name.
| For example: Domain/Invoicing/Database/Factories/InvoiceFactory.php
*/
'factories' => true,
],

/*
|--------------------------------------------------------------------------
| Base Model
Expand Down Expand Up @@ -146,4 +111,31 @@
|
*/
'base_action' => null,

/*
|--------------------------------------------------------------------------
| Autoloading
|--------------------------------------------------------------------------
|
| Configure whether domain providers, commands, policies, and factories
| should be auto-discovered and registered.
|
*/
'autoload' => [
'providers' => true,
'commands' => true,
'policies' => true,
'factories' => true,
],

/*
|--------------------------------------------------------------------------
| Caching
|--------------------------------------------------------------------------
|
| The folder where the domain cache files will be stored. Used for domain
| autoloading.
|
*/
'cache_directory' => 'bootstrap/cache',
];
20 changes: 20 additions & 0 deletions src/Commands/CacheClearCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Lunarstorm\LaravelDDD\Commands;

use Illuminate\Console\Command;
use Lunarstorm\LaravelDDD\Support\DomainCache;

class CacheClearCommand extends Command
{
protected $name = 'ddd:clear';

protected $description = 'Clear cached domain autoloaded objects.';

public function handle()
{
DomainCache::clear();

$this->info('Domain cache cleared.');
}
}
24 changes: 24 additions & 0 deletions src/Commands/CacheCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Lunarstorm\LaravelDDD\Commands;

use Illuminate\Console\Command;
use Lunarstorm\LaravelDDD\Support\DomainAutoloader;

class CacheCommand extends Command
{
protected $name = 'ddd:cache';

protected $description = 'Cache auto-discovered domain objects used for autoloading.';

public function handle()
{
DomainAutoloader::cacheProviders();

$this->info('Cached domain providers.');

DomainAutoloader::cacheCommands();

$this->info('Cached domain commands.');
}
}
4 changes: 2 additions & 2 deletions src/Commands/DomainFactoryMakeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ protected function preparePlaceholders(): array
// ]);

return [
'namespacedModel' => $domainModel->fqn,
'model' => class_basename($domainModel->fqn),
'namespacedModel' => $domainModel->fullyQualifiedName,
'model' => class_basename($domainModel->fullyQualifiedName),
'factory' => $this->getFactoryName(),
'namespace' => $domainFactory->namespace,
];
Expand Down
3 changes: 3 additions & 0 deletions src/LaravelDDDServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public function configurePackage(Package $package): void
->hasConfigFile()
->hasCommands([
Commands\InstallCommand::class,
Commands\CacheCommand::class,
Commands\CacheClearCommand::class,
Commands\DomainListCommand::class,
Commands\DomainModelMakeCommand::class,
Commands\DomainFactoryMakeCommand::class,
Expand Down Expand Up @@ -64,6 +66,7 @@ public function packageBooted()
public function packageRegistered()
{
(new DomainAutoloader())->autoload();

Event::subscribe(CacheClearSubscriber::class);
}
}
4 changes: 2 additions & 2 deletions src/Support/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function object(string $type, string $name): DomainObject
name: $name,
domain: $this->domain,
namespace: $namespace,
fqn: $namespace.'\\'.$name,
fullyQualifiedName: $namespace.'\\'.$name,
path: $this->path($namespace.'\\'.$name),
type: $type
);
Expand All @@ -128,7 +128,7 @@ public function factory(string $name): DomainObject
name: $name,
domain: $this->domain,
namespace: $this->namespace->factories,
fqn: $this->namespace->factories.'\\'.$name,
fullyQualifiedName: $this->namespace->factories.'\\'.$name,
path: str("database/factories/{$this->domainWithSubdomain}/{$name}.php")
->replace(['\\', '/'], DIRECTORY_SEPARATOR)
->toString(),
Expand Down
145 changes: 71 additions & 74 deletions src/Support/DomainAutoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Illuminate\Foundation\Application;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
Expand All @@ -21,90 +20,58 @@

class DomainAutoloader
{
protected string $cacheDirectory;

protected mixed $config;

public function __construct()
{
$this->config = config('ddd');
$this->cacheDirectory = $this->configValue('cache_directory') ?? 'bootstrap/cache/ddd';
}

protected function configValue($path)
{
return data_get($this->config, $path);
//
}

public function autoload(): void
{
if ($value = $this->configValue('autoload.providers')) {
$this->registerProviders($value);
if (! config()->has('ddd.autoload')) {
return;
}

if ($value = $this->configValue('autoload.commands')) {
$this->registerCommands($value);
$this->handleProviders();

if (app()->runningInConsole()) {
$this->handleCommands();
}

if ($this->configValue('autoload.policies') === true) {
$this->registerPolicies();
if (config('ddd.autoload.policies') === true) {
$this->handlePolicies();
}

if ($this->configValue('autoload.factories') === true) {
$this->registerFactories();
if (config('ddd.autoload.factories') === true) {
$this->handleFactories();
}
}

protected function normalizePaths($path): array
protected static function normalizePaths($path): array
{
return collect($path)
->filter(fn ($path) => is_dir($path))
->toArray();
}

protected function registerProviders(bool|string|array|null $path = null): void
protected function handleProviders(): void
{
$paths = $this->normalizePaths($path === true ? app()->basePath(DomainResolver::domainPath()) : $path);

$serviceProviders = $this->remember('ddd-domain-service-providers', static function () use ($paths) {
if (empty($paths)) {
return [];
}
$providers = DomainCache::has('domain-providers')
? DomainCache::get('domain-providers')
: static::discoverProviders();

$finder = Finder::create()->files()->in($paths);

return Lody::classesFromFinder($finder)
->isNotAbstract()
->isInstanceOf(ServiceProvider::class)
->toArray();
});

$app = app();

foreach ($serviceProviders as $serviceProvider) {
$app->register($serviceProvider);
foreach ($providers as $provider) {
app()->register($provider);
}
}

protected function registerCommands(bool|string|array|null $path = null): void
protected function handleCommands(): void
{
$paths = $this->normalizePaths($path === true ? app()->basePath(DomainResolver::domainPath()) : $path);
$commands = DomainCache::has('domain-commands')
? DomainCache::get('domain-commands')
: static::discoverCommands();

$commands = $this->remember('ddd-domain-commands', static function () use ($paths) {
if (empty($paths)) {
return [];
}

$finder = Finder::create()->files()->in($paths);

return Lody::classesFromFinder($finder)
->isNotAbstract()
->isInstanceOf(Command::class)
->toArray();
});

foreach ($commands as $class) {
$this->registerCommand($class);
foreach ($commands as $command) {
$this->registerCommand($command);
}
}

Expand All @@ -115,13 +82,13 @@ protected function registerCommand($class)
});
}

protected function registerPolicies(): void
protected function handlePolicies(): void
{
Gate::guessPolicyNamesUsing(static function (string $class): array|string {
if ($model = DomainObject::fromClass($class, 'model')) {
return (new Domain($model->domain))
->object('policy', "{$model->name}Policy")
->fqn;
->fullyQualifiedName;
}

$classDirname = str_replace('/', '\\', dirname(str_replace('\\', '/', $class)));
Expand All @@ -138,7 +105,7 @@ protected function registerPolicies(): void
});
}

protected function registerFactories(): void
protected function handleFactories(): void
{
Factory::guessFactoryNamesUsing(function (string $modelName) {
if (DomainResolver::isDomainClass($modelName)) {
Expand All @@ -155,30 +122,60 @@ protected function registerFactories(): void
});
}

protected function remember($fileName, $callback)
protected static function discoverProviders(): array
{
// The cache is not available during booting, so we need to roll our own file based cache
$cacheFilePath = base_path($this->cacheDirectory.'/'.$fileName.'.php');
$configValue = config('ddd.autoload.providers');

$data = file_exists($cacheFilePath) ? include $cacheFilePath : null;
if ($configValue === false) {
return [];
}

if (is_null($data)) {
$data = $callback();
$paths = static::normalizePaths(
$configValue === true ? app()->basePath(DomainResolver::domainPath()) : $configValue
);

file_put_contents(
$cacheFilePath,
'<?php '.PHP_EOL.'return '.var_export($data, true).';'
);
if (empty($paths)) {
return [];
}

return $data;
return Lody::classesFromFinder(Finder::create()->files()->in($paths))
->isNotAbstract()
->isInstanceOf(ServiceProvider::class)
->toArray();
}

public static function clearCache()
protected static function discoverCommands(): array
{
$files = glob(base_path(config('ddd.cache_directory').'/ddd-*.php'));
$configValue = config('ddd.autoload.commands');

if ($configValue === false) {
return [];
}

$paths = static::normalizePaths(
$configValue === true ?
app()->basePath(DomainResolver::domainPath())
: $configValue
);

if (empty($paths)) {
return [];
}

File::delete($files);
return Lody::classesFromFinder(Finder::create()->files()->in($paths))
->isNotAbstract()
->isInstanceOf(Command::class)
->toArray();
}

public static function cacheProviders(): void
{
DomainCache::set('domain-providers', static::discoverProviders());
}

public static function cacheCommands(): void
{
DomainCache::set('domain-commands', static::discoverCommands());
}

protected static function appNamespace()
Expand Down
Loading

0 comments on commit cfb5073

Please sign in to comment.