preset()->php(); arch()->preset()->security(); test('no shell execution functions are used') ->expect(['exec', 'shell_exec', 'system', 'passthru', 'proc_open', 'popen', 'pcntl_exec']) ->not->toBeUsed(); test('no eval usage') ->expect(['eval']) ->not->toBeUsed(); test('no dangerous file functions') ->expect(['file_put_contents']) ->not->toBeUsed(); test('no globals are used') ->expect(['$GLOBALS', 'global']) ->not->toBeUsed(); test('no singletons') ->expect(['getInstance', 'instance']) ->not->toBeUsed(); test('no insecure hashing') ->expect(['md5', 'sha1']) ->not->toBeUsed(); test('no sleep function are used') ->expect(['sleep', 'usleep', 'time_nanosleep', 'time_sleep_until']) ->not->toBeUsed(); test('no debug globals are used') ->expect(['var_dump', 'die', 'print_r', 'dd', 'dump']) ->not->toBeUsed(); arch('all source files use strict types') ->expect($libraryNamespace) ->toUseStrictTypes(); }); describe('naming conventions', function () use ($libraryNamespace): void { arch('mappers have Mapper suffix') ->expect("{$libraryNamespace}\Mapper") ->toHaveSuffix('Mapper'); arch('repositories have Repository suffix') ->expect("{$libraryNamespace}\Repository") ->toHaveSuffix('Repository'); arch('services have Service suffix') ->expect("{$libraryNamespace}\Service") ->toHaveSuffix('Service'); arch('errors have Error suffix') ->expect("{$libraryNamespace}\Error") ->toHaveSuffix('Error'); arch('factories have Factory suffix') ->expect("{$libraryNamespace}\Factory") ->toHaveSuffix('Factory'); arch('middlewares have Middleware suffix') ->expect([ ApiMandatoryHeadersMiddleware::class, ApiLoggerMiddleware::class, ]) ->toHaveSuffix('Middleware'); }); describe('inheritance contracts', function () use ($libraryNamespace): void { arch('ApiDtos implement Dto') ->expect("{$libraryNamespace}\Lib\ApiDto\Dto") ->toImplement(Dto::class) ->ignoring([ Dto::class, AbstractIncoCompoPartDto::class, IncoCompoPartDto::class, ]); arch('Errors implement ApiError') ->expect("{$libraryNamespace}\Error") ->toImplement(ApiError::class) ->ignoring([ApiError::class]); arch('Concrete errors extend AbstractApiError') ->expect("{$libraryNamespace}\Error") ->classes() ->toExtend(AbstractApiError::class) ->ignoring([AbstractApiError::class]); arch('Repositories implement Repository') ->expect("{$libraryNamespace}\Repository") ->toImplement(Repository::class) ->ignoring([Repository::class]); arch('Concrete repositories extend AbstractApiRepository') ->expect("{$libraryNamespace}\Repository") ->classes() ->toExtend(AbstractApiRepository::class) ->ignoring([AbstractApiRepository::class]); arch('Middlewares implement LibMiddleware') ->expect([ ApiMandatoryHeadersMiddleware::class, ApiLoggerMiddleware::class, ]) ->toImplement(LibMiddleware::class); arch('Sort columns implement SortColumn') ->expect([ EmployeeSortColumn::class, IncoSortColumn::class, ]) ->toImplement(SortColumn::class); arch('Value objects implement ValueObjectId') ->expect("{$libraryNamespace}\Lib\ApiValueObject") ->toImplement(ValueObjectId::class); }); describe('class modifiers and immutability', function () use ($libraryNamespace): void { arch('Api DTOs are final and readonly') ->expect("{$libraryNamespace}\Lib\ApiDto\Dto") ->classes() ->toBeReadonly() ->toBeFinal() ->ignoring([ Dto::class, AbstractIncoCompoPartDto::class, IncoCompoPartDto::class, PortionType::class, ]); arch('Value objects are final and readonly') ->expect("{$libraryNamespace}\Lib\ApiValueObject") ->classes() ->toBeReadonly() ->toBeFinal(); arch('Auth value objects are final and readonly') ->expect([ ClientIdentifier::class, ClientSecret::class, Nonce::class, ]) ->classes() ->toBeReadonly() ->toBeFinal(); arch('Concrete errors are final and readonly') ->expect("{$libraryNamespace}\Error") ->classes() ->toBeReadonly() ->toBeFinal() ->ignoring([ ApiError::class, AbstractApiError::class, ]); arch('Factories are final') ->expect("{$libraryNamespace}\Factory") ->classes() ->toBeFinal(); arch('Concrete services are final') ->expect("{$libraryNamespace}\Service") ->classes() ->toBeFinal(); arch('Concrete repositories are final') ->expect("{$libraryNamespace}\Repository") ->classes() ->toBeFinal() ->ignoring([AbstractApiRepository::class]); arch('Middlewares are final') ->expect([ ApiMandatoryHeadersMiddleware::class, ApiLoggerMiddleware::class, ]) ->classes() ->toBeFinal(); arch('Pagination Value objects are final and readonly') ->expect([ PageNumber::class, PageSize::class, ]) ->classes() ->toBeReadonly() ->toBeFinal(); arch('Pagination is final') ->expect(Pagination::class) ->toBeFinal(); }); describe('layer isolation', function () use ($libraryNamespace): void { arch('DTOs do not depend on Repositories, Services, or Mappers') ->expect("{$libraryNamespace}\Lib\ApiDto") ->not->toUse([ "{$libraryNamespace}\Repository", "{$libraryNamespace}\Service", "{$libraryNamespace}\Mapper", ]); arch('Value objects do not depend on Repositories, Services, Mappers, or DTOs') ->expect("{$libraryNamespace}\Lib\ApiValueObject") ->not->toUse([ "{$libraryNamespace}\Repository", "{$libraryNamespace}\Service", "{$libraryNamespace}\Mapper", "{$libraryNamespace}\Lib\ApiDto", ]); arch('Errors do not depend on Repositories, Services, Mappers, or DTOs') ->expect("{$libraryNamespace}\Error") ->not->toUse([ "{$libraryNamespace}\Repository", "{$libraryNamespace}\Service", "{$libraryNamespace}\Mapper", "{$libraryNamespace}\Lib\ApiDto", ]); arch('Repositories do not depend on Services') ->expect("{$libraryNamespace}\Repository") ->not->toUse([ "{$libraryNamespace}\Service", ]); arch('Mappers do not depend on Repositories or Services') ->expect("{$libraryNamespace}\Mapper") ->not->toUse([ "{$libraryNamespace}\Repository", "{$libraryNamespace}\Service", ]); });