*/ function enumsIn(string $namespace): array { $dir = dirname(__DIR__, 2) . '/src/' . str_replace('\\', '/', $namespace); $enums = []; if (! is_dir($dir)) { return $enums; } foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)) as $file) { $class = $namespace . str_replace([$dir, '.php', '/'], ['', '', '\\'], $file->getPathname()); if ($file->isFile() && enum_exists($class)) { $enums[] = $class; } } return $enums; } /** @return array */ function valueObjectsIn(string $baseNamespace): array { $dir = dirname(__DIR__, 2) . '/src/' . str_replace('\\', '/', $baseNamespace); $namespaces = []; if (! is_dir($dir)) { return $namespaces; } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { if ($item->isDir() && $item->getBasename() === 'ValueObjects') { $relativePath = str_replace([$dir, '/'], ['', '\\'], $item->getPathname()); $namespaces[] = $baseNamespace . $relativePath; } } return $namespaces; } describe('Application Architecture Rules', function (): void { arch('Application layer does not depend on Presentation, Infra, or App') ->expect('Application') ->not->toUse([ 'Presentation', 'Infra', 'Infrastructure', 'App', ]); arch('Application layer does not depend on Illuminate or Symfony frameworks') ->expect('Application') ->not->toUse([ 'Illuminate', 'Symfony', ]); arch('Application layer does not use Laravel helper functions') ->expect('Application') ->not->toUse([ 'abort', 'app', 'auth', 'asset', 'back', 'bcrypt', 'broadcast', 'cache', 'config', 'cookie', 'csrf_token', 'dd', 'dump', 'decrypt', 'dispatch', 'event', 'factory', 'logger', 'redirect', 'request', 'response', 'route', 'session', 'storage', 'url', 'throw_if', 'throw_unless', '__', 'trans', 'trans_choice', 'view', ]); arch('Application layer uses strict types') ->expect('Application') ->toUseStrictTypes(); arch('Application Usecase classes are final and readonly') ->expect('Application\Arcadia\Usecase') ->classes() ->toBeReadonly() ->toBeFinal() ->ignoring(enumsIn('Application\Arcadia\Usecase')); arch('Application layer must not contain Presenters') ->expect('Application') ->classes() ->not->toHaveSuffix('Presenter'); arch('Application layer must not contain Views') ->expect('Application') ->classes() ->not->toHaveSuffix('View'); arch('Application Value Objects are final and readonly') ->expect(valueObjectsIn('Application')) ->classes() ->toBeReadonly() ->toBeFinal() ->ignoring(enumsIn('Application')); });