Skip to content

Commit

Permalink
First pass for domain autoloader
Browse files Browse the repository at this point in the history
  • Loading branch information
pelmered committed Mar 25, 2024
1 parent 5e32899 commit a0a5c06
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 0 deletions.
35 changes: 35 additions & 0 deletions config/ddd.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,43 @@
'view_models' => 'ViewModels',
'value_objects' => 'ValueObjects',
'actions' => 'Actions',
'factories' => 'Database\Factories',
],


'autoload' => [
/*
| Autoload service providers from the domain namespace.
| By default, it loads any file that ends with 'ServiceProvider.php' inside your domain.
| For example: Domain/Invoicing/Providers/InvoicingServiceProvider.php or Domain/Invoicing/InvoicingServiceProvider.php
*/
// To customize the pattern, you can use a glob pattern like '*/Providers/*.php'
'service_providers' => '*/*ServiceProvider.php',

/*
| Autoload commands from the domain namespace.
| By default, it loads any file inside the /Commands folder that ends '.php', extends Illuminate\Console\Command and is not abstract.
| For example: Domain/Invoicing/Commands/CreateInvoiceCommand.php
*/
// To customize the pattern, you can use a glob pattern like '*/Commands/*.php'
'commands' => '*/Commands/*.php',

/*
| Autoload policies from the domain namespace.
| By default, it loads any file inside the /Policies folder that ends 'Policy.php' and is not abstract.
| For example: Domain/Invoicing/Policies/InvoicePolicy.php
*/
'policies' => 'Policies\\{model}Policy',

/*
| Autoload factories from the domain namespace.
| By default, it loads any file inside the /Database/Factories folder that ends 'Factory.php' and is not abstract.
| For example: Domain/Invoicing/Database/Factories/InvoiceFactory.php
*/
'factories' => 'Database\\Factories\\{model}Factory',
],


/*
|--------------------------------------------------------------------------
| Base Model
Expand Down
6 changes: 6 additions & 0 deletions src/LaravelDDDServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Lunarstorm\LaravelDDD\Commands\MakeModel;
use Lunarstorm\LaravelDDD\Commands\MakeValueObject;
use Lunarstorm\LaravelDDD\Commands\MakeViewModel;
use Lunarstorm\LaravelDDD\Support\DomainAutoloader;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;

Expand Down Expand Up @@ -45,4 +46,9 @@ public function packageBooted()
$this->package->basePath('/../stubs') => resource_path("stubs/{$this->package->shortName()}"),
], "{$this->package->shortName()}-stubs");
}

public function packageRegistered()
{
(new DomainAutoloader())->autoload();
}
}
154 changes: 154 additions & 0 deletions src/Support/DomainAutoloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php
namespace Lunarstorm\LaravelDDD\Support;

use Illuminate\Console\Application as ConsoleApplication;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Gate;
use ReflectionClass;

class DomainAutoloader
{
protected string $cacheDirectory;

protected mixed $config;

public function __construct()
{
$this->config = config('ddd');
}

public function autoload(): void
{
if(isset($this->config['autoload.service_providers'])) {
$this->registerDomainServiceProviders($this->config['autoload.service_providers']);
}
if(isset($this->config['autoload.commands'])) {
$this->registerDomainCommands($this->config['autoload.commands']);
}
if(isset($this->config['autoload.policies'])) {
$this->registerPolicies($this->config['autoload.policies']);
}
if(isset($this->config['autoload.factories'])) {
$this->registerFactories($this->config['autoload.factories']);
}
}

protected function registerDomainServiceProviders(bool|string $domainPath = null): void
{
$domainPath = is_string($domainPath) ? $domainPath : '*/*ServiceProvider.php';

$serviceProviders = Cache::rememberForever('ddd-domain-service-providers', static function () use ($domainPath){
return Arr::map(
glob(base_path(DomainResolver::getConfiguredDomainPath().'/'.$domainPath)),
(static function ($serviceProvider) {
return Path::filePathToNamespace(
$serviceProvider,
DomainResolver::getConfiguredDomainPath(),
DomainResolver::getConfiguredDomainNamespace()
);
}));
});

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

protected function registerDomainCommands(bool|string $domainPath = null): void
{
$domainPath = is_string($domainPath) ? $domainPath : '*/Commands/*.php';
$commands = Cache::rememberForever('ddd-domain-commands', static function () use ($domainPath){
$commands = Arr::map(
glob(base_path(DomainResolver::getConfiguredDomainPath().'/'.$domainPath)),
static function ($command) {
return Path::filePathToNamespace(
$command,
DomainResolver::getConfiguredDomainPath(),
DomainResolver::getConfiguredDomainNamespace()
);
});

// 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);
});
}
});
});
ConsoleApplication::starting(static function ($artisan) use ($commands): void {
foreach ($commands as $command) {
$artisan->resolve($command);
}
});
}

protected function registerPolicies(bool|string $domainPath = null): void
{
$domainPath = is_string($domainPath) ? $domainPath : 'Policies\\{model}Policy';

Gate::guessPolicyNamesUsing(static function (string $modelClass) use ($domainPath): ?string {

[$domain, $model] = static::extractDomainAndModelFromModelNamespace($modelClass);

if (is_null($domain)) {
return null;
}

$policy = DomainResolver::getConfiguredDomainNamespace().'\\'.$domain.'\\'.str_replace('{model}', $model, $domainPath);

return $policy;
});
}

protected function registerFactories(bool|string $domainPath = null): void
{
$domainPath = is_string($domainPath) ? $domainPath : 'Database\\Factories\\{model}Factory';

Factory::guessFactoryNamesUsing( function (string $modelClass) use ($domainPath){

[$domain, $model] = $this->extractDomainAndModelFromModelNamespace($modelClass);

if (is_null($domain)) {
return null;
}

// Look for domain model factory in \<DomainNamespace>\Database\\Factories\<model>Factory.php
$classPath = 'Domain\\'.$domain.'\\'.str_replace('{model}', $model, $domainPath);
if (class_exists($classPath)) {
return $classPath;
}

// Look for domain factory in /database/factories/<domain>/<model>Factory.php
$classPath = 'Database\\Factories\\'.$domain.'\\'.$model.'Factory';
if (class_exists($classPath)) {
return $classPath;
}

// Default Laravel factory location
return 'Database\Factories\\'.class_basename($modelClass).'Factory';
});
}

protected function extractDomainAndModelFromModelNamespace(string $modelName): array
{
// Matches <DomainNamespace>\{domain}\<ModelNamespace>\{model} and extracts domain and model
// For example: Domain\Invoicing\Models\Invoice gives ['domain' => 'Invoicing', 'model' => 'Invoice']
$regex = '/'.DomainResolver::getConfiguredDomainNamespace().'\\\\(?<domain>.+)\\\\'.$this->config['namespaces.models'].'\\\\(?<model>.+)/';

if (preg_match($regex, $modelName, $matches, PREG_OFFSET_CAPTURE, 0)) {
return [
'domain' => $matches['domain'][0],
'model' => $matches['model'][0]
];
}

return [];
}
}
9 changes: 9 additions & 0 deletions src/Support/Path.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ public static function join(...$parts)

return implode(DIRECTORY_SEPARATOR, $parts);
}

public static function filePathToNamespace(string $path, string $namespacePath, string $namespace): string
{
return str_replace(
[base_path().$namespacePath, '/', '.php'],
[$namespace, '\\', ''],
$path
);
}
}
8 changes: 8 additions & 0 deletions tests/Autoloader/AutoloadServiceProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Str;
use Lunarstorm\LaravelDDD\Tests\Fixtures\Enums\Feature;


0 comments on commit a0a5c06

Please sign in to comment.