From 29f7c6b1a71b91be4f53bd8b7576fc46e124431e Mon Sep 17 00:00:00 2001 From: Dmytro Vovk Date: Mon, 9 Jun 2025 14:50:41 +0200 Subject: [PATCH 1/3] feat:comprehensive codebase modernization, quality-tooling integration, and generator enhancements; feat: added PHPStan configuration and workflow; feat: added PHP_CodeSniffer configuration and workflow; feat: added PHPMD configuration, baseline, and updated workflow; feat: added Rector configuration and workflow feat: added Composer Unused configuration and composer-quality workflow feat: added Composer security audit workflow style: applied declare(strict_types=1) across codebase feat: introduced UIGeneratorTrait for UI component generation feat: added custom Rector rules for PHPUnit assertions and mock objects feat(Seeder): added WITH_TRANSACTIONS option for seeder execution feat(Generator): added example state method to factory stub feat(Generator): automatically name routes in generated route stubs feat(Generator): standardized date formatting for Seeder and Migration filenames feat(Generator): Covers the FORMAT_TIME constant and getDate() helper in Migration/Seeder generators refactor: updated Hashed ID decoding logic in Repositories, Covers the rewrite of decodeSearchQueryString and related helpers in Repository.php refactor: improved pagination handling and limit setting in Repositories, Covers changes to setPaginationLimit, wantsToSkipPagination, canSkipPagination in Repository.php refactor: updated Response class to use Symfony HTTP status constants refactor(Generator): updated string and parameter handling in Generator core. Covers changes to removeSpecialChars, getInput, checkParameterOr methods and removal of \Safe\ prefixes for preg_replace refactor(Generator): updated various generator stubs and commands logic refactor(User): moved model casts to dedicated casts() method refactor: standardized exception catching to \Throwable. Covers changes in CreateBookTask.php and Repository.php (create method) refactor(Foundation): updated glob usage in Configuration classes. Covers ApplicationBuilder.php and Apiato.php changing from \Safe\glob to glob refactor(Routing): improved API version resolution and prefixing. Covers changes in src/Foundation/Configuration/Routing.php fix(Action): ensured correct return type for transactional run closure fix(Generator): ensured correct parameter type hints in Generator class methods. Specifically for checkParameterOrAsk if the default value change was a fix fix(Generator): ensure relations are unloaded in generated 'create' event stubs fix(MacroServiceProvider): ensure correct this context for Config::unset macro fix(Foundation): improved base path and shared path resolution in Apiato class. Covers inferBasePath and sharedPath logic changes fix(Http): corrected RequestRelation method signatures and type hints. Covers return type change of isValidRelationOf and closure type hint chore: updated composer.json package name to dan-wolf-at/apiato-core chore: updated composer.json dependencies and script configurations chore: removed legacy static analysis configurations (PHPStan.dist, Psalm, old PHPMD) chore: updated GitHub Actions test matrix and CI workflow configurations. Covers general updates to tests.yaml, phpmd.yaml and matrix_includes.json not related to adding new tools fix: update minimum PHP version to 8.2 in Rector classes and adjust related messages; feat: updated PHP CodeSniffer and Rector configurations; --- .github/ISSUE_TEMPLATE/Bug_report.yml | 2 +- .github/workflows/composer-quality.yaml | 44 + .github/workflows/composer-security.yaml | 47 + .github/workflows/matrix_includes.json | 12 - .github/workflows/php-cs-fixer.yaml | 46 +- .github/workflows/phpcs.yaml | 49 + .github/workflows/phpmd.yaml | 41 +- .github/workflows/phpstan.yaml | 59 + .github/workflows/rector.yaml | 52 + .github/workflows/tests.yaml | 29 +- .php-cs-fixer.dist.php | 168 +- composer-unused.php | 13 + composer.json | 81 +- config/apiato.php | 6 +- phpcs.xml | 786 ++++ phpmd.baseline.xml | 58 + phpmd.ruleset.xml | 94 + phpstan-baseline.neon | 4177 +++++++++++++++++ phpstan.neon | 23 + phpstan.neon.dist | 16 - psalm.xml | 22 - rector.php | 125 +- ruleset.xml | 41 - src/Console/CommandServiceProvider.php | 2 + src/Console/Commands/ListActions.php | 13 +- src/Console/Commands/ListTasks.php | 13 +- src/Core/Actions/Action.php | 4 +- src/Core/Actions/SubAction.php | 2 + src/Core/Collections/EloquentCollection.php | 2 + src/Core/Console/Command.php | 2 + src/Core/Controllers/ApiController.php | 2 + src/Core/Controllers/BaseController.php | 2 + src/Core/Controllers/WebController.php | 2 + src/Core/Criteria/Criteria.php | 2 + src/Core/Events/Event.php | 2 + src/Core/Exceptions/Exception.php | 2 + src/Core/Exceptions/HttpException.php | 2 + src/Core/Factories/Factory.php | 2 + src/Core/Jobs/Job.php | 2 + src/Core/Listeners/Listener.php | 2 + src/Core/Mails/Mail.php | 2 + src/Core/Middleware/Middleware.php | 2 + src/Core/Models/BaseModel.php | 2 + .../Concerns/HandlesFactoryDiscovery.php | 5 +- .../HandlesHashedIdRouteModelBinding.php | 7 +- src/Core/Models/Concerns/HasHashedId.php | 8 +- src/Core/Models/InteractsWithApiato.php | 2 + src/Core/Models/Model.php | 2 + src/Core/Models/UserModel.php | 2 + src/Core/Notifications/Notification.php | 2 + src/Core/Policies/Policy.php | 2 + src/Core/Providers/EventServiceProvider.php | 2 + src/Core/Providers/RouteServiceProvider.php | 2 + src/Core/Providers/ServiceProvider.php | 2 + .../Exceptions/ResourceCreationFailed.php | 4 +- .../Exceptions/ResourceNotFound.php | 4 +- src/Core/Repositories/Repository.php | 277 +- src/Core/Requests/Request.php | 10 +- src/Core/Seeders/Seeder.php | 8 + src/Core/Tasks/Task.php | 2 + .../Testing/Concerns/PerformsAssertions.php | 14 +- src/Core/Testing/TestCase.php | 2 + src/Core/Transformers/Transformer.php | 14 +- src/Core/Values/Value.php | 2 + src/Foundation/Apiato.php | 122 +- .../Configuration/ApplicationBuilder.php | 141 +- src/Foundation/Configuration/Factory.php | 8 +- src/Foundation/Configuration/Localization.php | 9 +- src/Foundation/Configuration/Provider.php | 10 +- src/Foundation/Configuration/Repository.php | 10 +- src/Foundation/Configuration/Routing.php | 63 +- src/Foundation/Configuration/Seeding.php | 21 +- src/Foundation/Configuration/View.php | 9 +- src/Foundation/Database/DatabaseSeeder.php | 11 +- .../Providers/ApiatoServiceProvider.php | 4 +- .../Providers/ConfigServiceProvider.php | 2 + .../Providers/HelperServiceProvider.php | 2 + .../Providers/LocalizationServiceProvider.php | 8 +- .../Providers/MigrationServiceProvider.php | 2 + .../Providers/RateLimitingServiceProvider.php | 2 + .../Support/Providers/ViewServiceProvider.php | 8 +- src/Foundation/helpers.php | 10 +- src/Generator/Commands/ActionGenerator.php | 27 +- .../Commands/ConfigurationGenerator.php | 21 +- .../Commands/ContainerApiGenerator.php | 364 +- src/Generator/Commands/ContainerGenerator.php | 71 +- .../Commands/ContainerWebGenerator.php | 266 +- .../Commands/ControllerGenerator.php | 29 +- src/Generator/Commands/EventGenerator.php | 34 +- .../Commands/EventListenerGenerator.php | 18 +- src/Generator/Commands/ExceptionGenerator.php | 20 +- .../Commands/FunctionalTestGenerator.php | 36 +- src/Generator/Commands/JobGenerator.php | 21 +- src/Generator/Commands/MailGenerator.php | 29 +- .../Commands/MiddlewareGenerator.php | 21 +- src/Generator/Commands/MigrationGenerator.php | 33 +- .../Commands/ModelFactoryGenerator.php | 24 +- src/Generator/Commands/ModelGenerator.php | 31 +- .../Commands/NotificationGenerator.php | 20 +- src/Generator/Commands/PolicyGenerator.php | 21 +- src/Generator/Commands/ReadmeGenerator.php | 22 +- .../Commands/RepositoryGenerator.php | 24 +- src/Generator/Commands/RequestGenerator.php | 16 +- src/Generator/Commands/RouteGenerator.php | 62 +- src/Generator/Commands/SeederGenerator.php | 40 +- .../Commands/ServiceProviderGenerator.php | 27 +- src/Generator/Commands/SubActionGenerator.php | 25 +- src/Generator/Commands/TaskGenerator.php | 31 +- src/Generator/Commands/TestCaseGenerator.php | 30 +- .../Commands/TransformerGenerator.php | 39 +- src/Generator/Commands/UnitTestGenerator.php | 39 +- src/Generator/Commands/ValueGenerator.php | 23 +- src/Generator/Generator.php | 125 +- src/Generator/GeneratorsServiceProvider.php | 2 + .../Interfaces/ComponentsGenerator.php | 4 +- src/Generator/Stubs/actions/create.stub | 2 + src/Generator/Stubs/actions/delete.stub | 2 + src/Generator/Stubs/actions/find.stub | 2 + src/Generator/Stubs/actions/generic.stub | 4 +- src/Generator/Stubs/actions/list.stub | 2 + src/Generator/Stubs/actions/update.stub | 4 +- src/Generator/Stubs/config.stub | 2 + .../Stubs/controllers/api/create.stub | 2 + src/Generator/Stubs/controllers/api/crud.stub | 2 + .../Stubs/controllers/api/delete.stub | 2 + src/Generator/Stubs/controllers/api/find.stub | 2 + .../Stubs/controllers/api/generic.stub | 2 + src/Generator/Stubs/controllers/api/list.stub | 2 + .../Stubs/controllers/api/update.stub | 2 + .../Stubs/controllers/web/create.stub | 2 + src/Generator/Stubs/controllers/web/crud.stub | 2 + .../Stubs/controllers/web/delete.stub | 2 + src/Generator/Stubs/controllers/web/edit.stub | 2 + src/Generator/Stubs/controllers/web/find.stub | 2 + .../Stubs/controllers/web/generic.stub | 2 + src/Generator/Stubs/controllers/web/list.stub | 2 + .../Stubs/controllers/web/store.stub | 2 + .../Stubs/controllers/web/update.stub | 2 + src/Generator/Stubs/events/create.stub | 4 + src/Generator/Stubs/events/delete.stub | 7 +- src/Generator/Stubs/events/find.stub | 2 + src/Generator/Stubs/events/generic.stub | 2 + src/Generator/Stubs/events/list.stub | 2 + src/Generator/Stubs/events/update.stub | 2 + src/Generator/Stubs/exception.stub | 2 + src/Generator/Stubs/factory.stub | 13 +- src/Generator/Stubs/job.stub | 2 + src/Generator/Stubs/listeners/listener.stub | 2 + src/Generator/Stubs/mail.stub | 2 + src/Generator/Stubs/middleware.stub | 2 + src/Generator/Stubs/migration.stub | 6 +- src/Generator/Stubs/model.stub | 2 + src/Generator/Stubs/notification.stub | 2 + src/Generator/Stubs/policy.stub | 5 + .../providers/event-service-provider.stub | 4 +- .../Stubs/providers/service-provider.stub | 2 + src/Generator/Stubs/repository.stub | 5 + src/Generator/Stubs/requests/create.stub | 2 + src/Generator/Stubs/requests/delete.stub | 2 + src/Generator/Stubs/requests/edit.stub | 2 + src/Generator/Stubs/requests/find.stub | 2 + src/Generator/Stubs/requests/generic.stub | 2 + src/Generator/Stubs/requests/list.stub | 2 + src/Generator/Stubs/requests/store.stub | 2 + src/Generator/Stubs/requests/update.stub | 2 + src/Generator/Stubs/routes/api.mac.stub | 14 +- src/Generator/Stubs/routes/api.sac.stub | 14 +- src/Generator/Stubs/routes/generic.stub | 4 +- src/Generator/Stubs/routes/web.mac.stub | 4 +- src/Generator/Stubs/routes/web.sac.stub | 4 +- src/Generator/Stubs/seeder.stub | 2 + src/Generator/Stubs/subaction.stub | 6 +- src/Generator/Stubs/tasks/create.stub | 2 + src/Generator/Stubs/tasks/delete.stub | 4 +- src/Generator/Stubs/tasks/find.stub | 4 +- src/Generator/Stubs/tasks/generic.stub | 4 +- src/Generator/Stubs/tasks/list.stub | 6 +- src/Generator/Stubs/tasks/update.stub | 4 +- .../Stubs/tasks/with_event/create.stub | 2 + .../Stubs/tasks/with_event/delete.stub | 4 +- .../Stubs/tasks/with_event/find.stub | 4 +- .../Stubs/tasks/with_event/generic.stub | 2 + .../Stubs/tasks/with_event/list.stub | 6 +- .../Stubs/tasks/with_event/update.stub | 4 +- src/Generator/Stubs/tests/e2e/web.stub | 5 + src/Generator/Stubs/tests/functional/api.stub | 5 + src/Generator/Stubs/tests/functional/cli.stub | 5 + .../Stubs/tests/functional/create.stub | 8 +- .../Stubs/tests/functional/delete.stub | 5 + .../Stubs/tests/functional/find.stub | 7 +- .../Stubs/tests/functional/generic.stub | 2 + .../Stubs/tests/functional/list.stub | 7 +- .../Stubs/tests/functional/update.stub | 7 +- src/Generator/Stubs/tests/testcase/api.stub | 2 + src/Generator/Stubs/tests/testcase/cli.stub | 2 + .../Stubs/tests/testcase/container.stub | 2 + src/Generator/Stubs/tests/testcase/e2e.stub | 2 + .../Stubs/tests/testcase/functional.stub | 2 + src/Generator/Stubs/tests/testcase/unit.stub | 2 + src/Generator/Stubs/tests/testcase/web.stub | 2 + .../Stubs/tests/unit/data/migration.stub | 4 +- src/Generator/Stubs/tests/unit/factory.stub | 7 +- src/Generator/Stubs/tests/unit/generic.stub | 2 + .../Stubs/tests/unit/tasks/create.stub | 2 + .../Stubs/tests/unit/tasks/delete.stub | 2 + .../Stubs/tests/unit/tasks/find.stub | 2 + .../Stubs/tests/unit/tasks/list.stub | 2 + .../Stubs/tests/unit/tasks/update.stub | 8 +- .../tests/unit/tasks/with_event/create.stub | 2 + .../tests/unit/tasks/with_event/delete.stub | 4 +- .../tests/unit/tasks/with_event/find.stub | 4 +- .../tests/unit/tasks/with_event/list.stub | 6 +- .../tests/unit/tasks/with_event/update.stub | 8 +- src/Generator/Stubs/transformer.stub | 6 +- src/Generator/Stubs/value.stub | 2 + src/Generator/Traits/FileSystemTrait.php | 20 +- src/Generator/Traits/FormatterTrait.php | 23 - src/Generator/Traits/ParserTrait.php | 32 +- src/Generator/Traits/PrinterTrait.php | 6 +- src/Generator/Traits/UIGeneratorTrait.php | 66 + src/Http/Middleware/ProcessETag.php | 4 +- src/Http/Middleware/ValidateJsonContent.php | 2 + src/Http/RequestRelation.php | 25 +- src/Http/Resources/Collection.php | 12 +- src/Http/Resources/HasResourceKey.php | 2 + src/Http/Resources/Item.php | 4 +- src/Http/Resources/ResourceKeyAware.php | 2 + src/Http/Response.php | 142 +- .../AssertInstanceToStaticCallRector.php | 131 + .../MockObjectStaticToInstanceCallRector.php | 119 + src/Macros/MacroServiceProvider.php | 22 +- src/Support/DefaultProviders.php | 2 + src/Support/Facades/Response.php | 22 +- src/Support/HashidsManagerDecorator.php | 25 +- src/Support/Sanitizer.php | 4 +- tests/Arch/CoreTest.php | 5 +- .../Console/CommandServiceProviderTest.php | 2 + .../Console/Commands/ListActionsTest.php | 2 + .../Console/Commands/ListTasksTest.php | 2 + .../Functional/Core/Models/BaseModelTest.php | 8 +- .../Core/Repositories/RepositoryTest.php | 29 +- .../Functional/Core/Requests/RequestTest.php | 79 +- .../Configuration/ApplicationBuilderTest.php | 2 + .../Providers/ApiatoServiceProviderTest.php | 2 + .../Providers/ConfigServiceProviderTest.php | 3 + .../Providers/HelperServiceProviderTest.php | 2 + .../LocalizationServiceProviderTest.php | 12 +- .../MigrationServiceProviderTest.php | 2 + .../Providers/ViewServiceProviderTest.php | 2 + tests/Functional/IncludeEagerLoadingTest.php | 4 +- .../Macros/MacroServiceProviderTest.php | 2 +- tests/FunctionalTestCase.php | 2 + tests/Pest.php | 4 +- tests/TestCase.php | 4 +- tests/Unit/Core/Actions/ActionTest.php | 6 +- tests/Unit/Core/Models/BaseModelTest.php | 64 +- .../Exceptions/ResourceCreationFailedTest.php | 10 +- .../Exceptions/ResourceNotFoundTest.php | 10 +- .../Unit/Core/Repositories/RepositoryTest.php | 319 +- tests/Unit/Core/Requests/RequestTest.php | 6 +- tests/Unit/Core/Tests/TestCaseTest.php | 5 +- .../Core/Transformers/TransformerTest.php | 8 +- tests/Unit/Foundation/ApiatoTest.php | 12 +- .../Foundation/Configuration/FactoryTest.php | 2 + .../Configuration/LocalizationTest.php | 2 + .../Foundation/Configuration/ProviderTest.php | 4 +- .../Configuration/RepositoryTest.php | 2 + .../Foundation/Configuration/RoutingTest.php | 2 + .../Foundation/Configuration/SeedingTest.php | 2 + .../Foundation/Configuration/ViewTest.php | 2 + .../Database/DatabaseSeederTest.php | 4 +- tests/Unit/Foundation/HelpersTest.php | 25 +- .../Unit/Http/Middleware/ProcessETagTest.php | 2 + .../Middleware/ValidateJsonContentTest.php | 4 + tests/Unit/Http/RequestRelationTest.php | 2 +- tests/Unit/Http/Resources/CollectionTest.php | 20 +- tests/Unit/Http/Resources/ItemTest.php | 20 +- tests/Unit/Http/ResponseTest.php | 125 +- .../Unit/Macros/MacroServiceProviderTest.php | 2 + tests/Unit/Support/DefaultProvidersTest.php | 2 + tests/Unit/Support/Facades/ResponseTest.php | 120 +- .../Support/HashidsManagerDecoratorTest.php | 46 +- tests/Unit/Support/SanitizerTest.php | 7 +- tests/UnitTestCase.php | 2 + .../User/Data/Factories/UserFactory.php | 16 +- ...01_000000_testbench_create_users_table.php | 4 +- ...12_000000_testbench_update_users_table.php | 4 +- .../User/Data/Repositories/UserRepository.php | 2 + .../Containers/Identity/User/Models/User.php | 13 +- .../UI/API/Transformers/UserTransformer.php | 12 +- .../MySection/Author/Actions/SimpleAction.php | 2 + .../Author/Data/Seeders/Murdered_2.php | 2 + .../Author/Data/Seeders/Ordered_1.php | 2 + .../Author/Data/Seeders/Unordered.php | 2 + .../Author/Data/Seeders/Wondered_3.php | 2 + .../MySection/Author/Events/AuthorCreated.php | 5 +- .../MySection/Author/Helpers/helpers.php | 2 + .../Listeners/AuthorCreatedListener.php | 3 + .../Author/Mails/Templates/author.blade.php | 6 + .../UI/API/Routes/ListAuthors.v3.public.php | 2 + .../Author/UI/WEB/Routes/ListAuthors.php | 2 + .../Book/Actions/CreateBookAction.php | 2 + .../MySection/Book/Configs/mySection-book.php | 2 + .../Book/Data/Factories/BookFactory.php | 8 +- .../2024_12_29_144159_create_books_table.php | 4 +- .../Book/Data/Repositories/BookRepository.php | 2 + .../Book/Data/Seeders/Murdered_2.php | 2 + .../MySection/Book/Data/Seeders/Ordered_1.php | 2 + .../MySection/Book/Data/Seeders/Unordered.php | 2 + .../Book/Data/Seeders/Wondered_3.php | 2 + .../MySection/Book/Events/BookCreated.php | 7 +- .../MySection/Book/Helpers/functions.php | 2 + .../MySection/Book/Languages/en/errors.php | 2 + .../MySection/Book/Languages/fa/errors.php | 2 + .../Book/Listeners/BookCreatedListener.php | 3 + .../Book/Middlewares/BeforeMiddleware.php | 2 + .../Containers/MySection/Book/Models/Book.php | 2 + .../Book/Providers/BookServiceProvider.php | 4 +- .../Book/Providers/EventServiceProvider.php | 2 + .../MySection/Book/Tasks/CreateBookTask.php | 9 +- .../API/Controllers/CreateBookController.php | 2 + .../API/Controllers/UpdateBookController.php | 54 +- .../UI/API/Requests/CreateBookRequest.php | 2 + .../UI/API/Requests/UpdateBookRequest.php | 12 +- .../UI/API/Routes/CreateBook.v1.private.php | 2 + .../ImplicitRouteModelBinding.v1.private.php | 2 + .../UI/API/Routes/ListBooks.v4.private.php | 2 + .../UI/API/Routes/UpdateBook.v1.private.php | 2 + .../UI/API/Transformers/BookTransformer.php | 10 +- .../UI/CLI/Commands/ContainerTestCommand.php | 2 + .../WEB/Controllers/CreateBookController.php | 6 +- .../UI/WEB/Requests/CreateBookRequest.php | 2 + .../UI/WEB/Routes/CreateBook.v1.public.php | 2 + .../Book/UI/WEB/Routes/ListBooks.php | 2 + .../Book/UI/WEB/Views/book-me.blade.php | 6 + .../Data/Repositories/MultiWordRepository.php | 2 + .../MySection/MultiWord/Models/MultiWord.php | 2 + .../Comment/Data/Factories/CommentFactory.php | 4 +- ...025_01_25_224724_create_comments_table.php | 4 +- .../Comment/Models/Comment.php | 2 + .../Like/Data/Factories/LikeFactory.php | 2 + .../2025_01_25_234724_create_likes_table.php | 4 +- ...025_01_25_234824_create_likables_table.php | 4 +- .../SocialInteraction/Like/Models/Like.php | 2 + .../app/Ship/Commands/ShipTestCommand.php | 3 + workbench/app/Ship/Configs/boat.php | 2 + workbench/app/Ship/Configs/fractal.php | 6 +- workbench/app/Ship/Configs/repository.php | 34 +- .../Ship/Exceptions/CreateResourceFailed.php | 4 +- .../app/Ship/Exceptions/ResourceNotFound.php | 4 +- workbench/app/Ship/Helpers/ExplosiveClass.php | 2 + workbench/app/Ship/Helpers/functions.php | 2 + workbench/app/Ship/Helpers/helpers.php | 2 + workbench/app/Ship/Languages/en/errors.php | 2 + workbench/app/Ship/Languages/fa/errors.php | 2 + .../Mails/Templates/welcome-cheers.blade.php | 6 + ...01_000001_testbench_create_cache_table.php | 4 +- ..._01_000002_testbench_create_jobs_table.php | 4 +- ...0_testbench_create_notifications_table.php | 4 +- ...00_01_01_000000_create_ship_test_table.php | 4 +- workbench/app/Ship/Parents/Actions/Action.php | 2 + .../app/Ship/Parents/Actions/SubAction.php | 2 + .../Collections/EloquentCollection.php | 2 + .../app/Ship/Parents/Commands/Command.php | 2 + .../Parents/Controllers/ApiController.php | 2 + .../Parents/Controllers/WebController.php | 2 + .../app/Ship/Parents/Criterias/Criteria.php | 2 + workbench/app/Ship/Parents/Events/Event.php | 2 + .../app/Ship/Parents/Exceptions/Exception.php | 2 + .../Ship/Parents/Exceptions/HttpException.php | 2 + .../app/Ship/Parents/Factories/Factory.php | 2 + workbench/app/Ship/Parents/Jobs/Job.php | 2 + .../app/Ship/Parents/Listeners/Listener.php | 2 + workbench/app/Ship/Parents/Mails/Mail.php | 2 + .../Ship/Parents/Middleware/Middleware.php | 2 + workbench/app/Ship/Parents/Models/Model.php | 2 + .../app/Ship/Parents/Models/UserModel.php | 2 + .../Parents/Notifications/Notification.php | 2 + .../app/Ship/Parents/Policies/Policy.php | 2 + .../Providers/EventServiceProvider.php | 2 + .../Parents/Providers/ServiceProvider.php | 2 + .../Ship/Parents/Repositories/Repository.php | 2 + .../app/Ship/Parents/Requests/Request.php | 2 + workbench/app/Ship/Parents/Seeders/Seeder.php | 2 + workbench/app/Ship/Parents/Tasks/Task.php | 2 + workbench/app/Ship/Parents/Tests/TestCase.php | 2 + .../Ship/Parents/Transformers/Transformer.php | 2 + workbench/app/Ship/Parents/Values/Value.php | 2 + .../Ship/Providers/ShipServiceProvider.php | 2 + workbench/app/Ship/Views/something.blade.php | 6 + workbench/app/StrayServiceProvider.php | 2 + workbench/bootstrap/app.php | 4 +- 392 files changed, 9080 insertions(+), 1840 deletions(-) create mode 100644 .github/workflows/composer-quality.yaml create mode 100644 .github/workflows/composer-security.yaml create mode 100644 .github/workflows/phpcs.yaml create mode 100644 .github/workflows/phpstan.yaml create mode 100644 .github/workflows/rector.yaml create mode 100644 composer-unused.php create mode 100644 phpcs.xml create mode 100644 phpmd.baseline.xml create mode 100644 phpmd.ruleset.xml create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon delete mode 100644 phpstan.neon.dist delete mode 100644 psalm.xml delete mode 100644 ruleset.xml delete mode 100644 src/Generator/Traits/FormatterTrait.php create mode 100644 src/Generator/Traits/UIGeneratorTrait.php create mode 100644 src/Linters/Rector/AssertInstanceToStaticCallRector.php create mode 100644 src/Linters/Rector/MockObjectStaticToInstanceCallRector.php diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index d7173ece8..694259057 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -15,7 +15,7 @@ body: attributes: label: PHP Version description: Provide the PHP version that you are using. - placeholder: 8.1.x + placeholder: 8.2.x validations: required: true - type: input diff --git a/.github/workflows/composer-quality.yaml b/.github/workflows/composer-quality.yaml new file mode 100644 index 000000000..32ef9ad3a --- /dev/null +++ b/.github/workflows/composer-quality.yaml @@ -0,0 +1,44 @@ +name: "Composer - quality" + +on: + push: + branches: [ master, '*.x' ] + pull_request: + types: [ opened, synchronize, reopened ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + composer-quality: + name: Composer Quality Checks + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: dom, curl, libxml, mbstring, zip + coverage: none + + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + composer-options: --prefer-dist --optimize-autoloader + + - name: Validate composer.json / composer.lock + run: composer validate --strict --no-check-publish --ansi + + - name: Detect unused packages + run: vendor/bin/composer-unused --no-progress --ansi diff --git a/.github/workflows/composer-security.yaml b/.github/workflows/composer-security.yaml new file mode 100644 index 000000000..22b5f0ee4 --- /dev/null +++ b/.github/workflows/composer-security.yaml @@ -0,0 +1,47 @@ +name: "Composer - security audit" + +on: + push: + branches: [ master, '*.x' ] + pull_request: + types: [ opened, synchronize, reopened ] + schedule: + - cron: '0 6 * * *' # daily at 06:00 UTC + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + composer-security: + name: Composer Security Audit + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: dom, curl, libxml, mbstring, zip + coverage: none + + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + dependency-versions: locked + composer-options: --prefer-dist + + - name: Run composer audit + run: composer audit --locked --ansi + + - name: Run security check (roave/security-advisories) + run: composer update --dry-run --ansi --quiet --no-scripts --no-interaction roave/security-advisories diff --git a/.github/workflows/matrix_includes.json b/.github/workflows/matrix_includes.json index 78cb73101..41a1b187a 100644 --- a/.github/workflows/matrix_includes.json +++ b/.github/workflows/matrix_includes.json @@ -34,17 +34,5 @@ "runOn":"13.x", "php_version":"8.4", "php_unit_version": "phpunit:11.*" - }, - { - "runs_on": "ubuntu-latest", - "runOn":"8.x", - "php_version":"8.1", - "php_unit_version": "phpunit:10.*" - }, - { - "runs_on": "ubuntu-latest", - "runOn":"8.x", - "php_version":"8.2", - "php_unit_version": "phpunit:10.*" } ] diff --git a/.github/workflows/php-cs-fixer.yaml b/.github/workflows/php-cs-fixer.yaml index bdf3f93a6..bcbc3479c 100644 --- a/.github/workflows/php-cs-fixer.yaml +++ b/.github/workflows/php-cs-fixer.yaml @@ -1,35 +1,49 @@ -name: Check & fix styling +name: "Linter - PHP-CS-Fixer" on: push: - branches: - - master - - '*.x' + branches: [ master, '*.x' ] pull_request: types: [ opened, synchronize, reopened ] +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: php-cs-fixer: name: PHP-CS-Fixer runs-on: ubuntu-latest - permissions: - contents: write timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v4 - - name: Use Cache + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite + coverage: none + + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Cache PHP-CS-Fixer results uses: actions/cache@v4 with: path: .php-cs-fixer.cache - key: ${{ runner.OS }}-${{ github.repository }}-phpcsfixer-${{ github.sha }} + key: ${{ runner.os }}-php-cs-fixer-${{ github.sha }} restore-keys: | - ${{ runner.OS }}-${{ github.repository }}-phpcsfixer- - - name: Run PHP CS Fixer - uses: docker://oskarstark/php-cs-fixer-ga - with: - args: --config=.php-cs-fixer.dist.php - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 + ${{ runner.os }}-php-cs-fixer- + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 with: - commit_message: "style(automated): apply php-cs-fixer rules" + composer-options: --prefer-dist + + - name: Run PHP-CS-Fixer + run: vendor/bin/php-cs-fixer fix --dry-run --format=txt --diff --allow-risky=yes --using-cache=yes --ansi -vv diff --git a/.github/workflows/phpcs.yaml b/.github/workflows/phpcs.yaml new file mode 100644 index 000000000..097915c9e --- /dev/null +++ b/.github/workflows/phpcs.yaml @@ -0,0 +1,49 @@ +name: "Linter - PHP_CodeSniffer" + +on: + push: + branches: [ master, '*.x' ] + pull_request: + types: [ opened, synchronize, reopened ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + phpcs: + name: PHP_CodeSniffer + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite + coverage: none + + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Cache PHPCS results + uses: actions/cache@v4 + with: + path: .phpcs-cache + key: ${{ runner.os }}-phpcs-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-phpcs- + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + composer-options: --prefer-dist + + - name: Run PHP_CodeSniffer + run: vendor/bin/phpcs -p --error-severity=1 --warning-severity=6 --colors --cache=.phpcs-cache diff --git a/.github/workflows/phpmd.yaml b/.github/workflows/phpmd.yaml index 48c0eb999..0a483fba9 100644 --- a/.github/workflows/phpmd.yaml +++ b/.github/workflows/phpmd.yaml @@ -1,25 +1,50 @@ -name: PHPMD +name: "Linter - PHPMD" on: push: - branches: - - master - - '*.x' + branches: [ master, '*.x' ] pull_request: types: [ opened, synchronize, reopened ] +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: phpmd: - name: PHPMD + name: PHP Mess Detector runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v4 - - name: Setup PHP environment + + - name: Setup PHP uses: shivammathur/setup-php@v2 with: + php-version: '8.3' + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite coverage: none - tools: phpmd + + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Cache PHPMD results + uses: actions/cache@v4 + with: + path: .phpmd.result-cache.php + key: ${{ runner.os }}-phpmd-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-phpmd- + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + composer-options: --prefer-dist + - name: Run PHPMD - run: phpmd . github ruleset.xml --exclude 'tests/*,vendor/*' + run: vendor/bin/phpmd . github phpmd.ruleset.xml --ignore-violations-on-exit --color --cache + diff --git a/.github/workflows/phpstan.yaml b/.github/workflows/phpstan.yaml new file mode 100644 index 000000000..33fc0dce4 --- /dev/null +++ b/.github/workflows/phpstan.yaml @@ -0,0 +1,59 @@ +name: "Static analysis - PHPStan" + +on: + push: + branches: [ master, '*.x' ] + pull_request: + types: [ opened, synchronize, reopened ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + phpstan: + name: PHPStan Static Analysis + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite + coverage: none + + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + composer-options: --prefer-dist + + - name: Prepare PHPStan cache directory + run: mkdir -p ./tmp/phpstan + + - name: Restore PHPStan result cache + uses: actions/cache/restore@v4 + with: + path: ./tmp/phpstan + key: ${{ runner.os }}-phpstan-result-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-phpstan-result-cache- + + - name: Run PHPStan + run: vendor/bin/phpstan analyse --memory-limit=512M --ansi --error-format=github + + - name: Save PHPStan result cache + uses: actions/cache/save@v4 + if: ${{ !cancelled() }} + with: + path: ./tmp/phpstan + key: phpstan-result-cache-${{ github.sha }} diff --git a/.github/workflows/rector.yaml b/.github/workflows/rector.yaml new file mode 100644 index 000000000..fd1ff1fe2 --- /dev/null +++ b/.github/workflows/rector.yaml @@ -0,0 +1,52 @@ +name: "Refactor - Rector" + +on: + push: + branches: [ master, '*.x' ] + pull_request: + types: [ opened, synchronize, reopened ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rector: + name: Rector Code Analysis + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite + coverage: none + + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Prepare Rector cache directory + run: mkdir -p ./tmp/rector + + - name: Cache Rector results + uses: actions/cache@v4 + with: + path: ./tmp/rector + key: ${{ runner.os }}-rector-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-rector- + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + with: + composer-options: --prefer-dist + + - name: Run Rector Dry Run + run: vendor/bin/rector process --dry-run --no-progress-bar --output-format=github --ansi diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c25980089..16a93cc74 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,15 +1,17 @@ -name: Tests +name: "Test suite" on: push: - branches: - - master - - '*.x' + branches: [ master, '*.x' ] pull_request: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: matrix_prep: runs-on: ubuntu-latest @@ -42,14 +44,27 @@ jobs: coverage: xdebug tools: ${{ matrix.php_unit_version }} + - name: Setup Composer token + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + + - name: Cache PHPUnit results + uses: actions/cache@v4 + with: + path: .phpunit.result.cache + key: ${{ runner.os }}-php-${{ matrix.php }}-phpunit-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-php-${{ matrix.php }}-phpunit- + - name: Install Composer dependencies - run: composer install --prefer-dist --no-interaction --no-progress + uses: ramsey/composer-install@v3 + with: + composer-options: --prefer-dist - name: Execute tests - run: vendor/bin/pest --ci --coverage-clover=coverage.xml + run: vendor/bin/pest --ci --coverage-clover=coverage.xml --cache-directory=.phpunit.result.cache --order-by=random --colors=always --display-deprecations --display-incomplete --display-notices --display-warnings - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: true verbose: true diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 4d592738a..ef1357e08 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,36 +1,142 @@ in([ - __DIR__ . '/src', - __DIR__ . '/tests', - __DIR__ . '/workbench', - ]) - ->name('*.php') - ->notName('*.blade.php') - ->exclude([ - 'Support/Doubles/Fakes/Laravel/bootstrap', - 'Support/Doubles/Fakes/Laravel/storage', - ]); - -return (new PhpCsFixer\Config()) +declare(strict_types=1); + +/** + * @link https://mlocati.github.io/php-cs-fixer-configurator/ + * @link https://cs.symfony.com/doc/usage.html + */ +/** Don't use finder for vscode. It will be slow. */ +$isVSCodeRun = isset($_SERVER['VSCODE_AGENT_FOLDER']); +$finder = []; +if ($isVSCodeRun === false) { + $finder = PhpCsFixer\Finder::create() + ->ignoreVCS(true) + ->ignoreDotFiles(true) + ->name('*.php') + ->notName(['*.blade.php']) + ->in([ + __DIR__ . '/src', + __DIR__ . '/tests', + __DIR__ . '/workbench', + ]) + ->ignoreVCSIgnored(true) + ->exclude([ + 'Support/Doubles/Fakes/Laravel/bootstrap', + 'Support/Doubles/Fakes/Laravel/storage', + ]); +} +$config = new PhpCsFixer\Config(); + +return $config + ->setRiskyAllowed(true) ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) ->setRules([ - '@Symfony' => true, - 'concat_space' => ['spacing' => 'one'], - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - ], - 'phpdoc_align' => [ - 'align' => 'left', - ], - 'trailing_comma_in_multiline' => [ - 'elements' => ['arguments', 'arrays', 'match', 'parameters'], + // Global: + '@PSR12' => true, + 'psr_autoloading' => true, + 'single_quote' => true, + 'no_mixed_echo_print' => [ + 'use' => 'echo', ], - 'blank_line_before_statement' => [ - 'statements' => ['return', 'throw', 'try'], - ], - 'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => true], - 'nullable_type_declaration' => ['syntax' => 'union'], - ]) - ->setFinder($finder); + 'heredoc_to_nowdoc' => true, + 'increment_style' => ['style' => 'post'], + 'no_empty_statement' => true, + 'no_short_bool_cast' => true, + 'no_unneeded_control_parentheses' => true, + 'self_accessor' => true, + 'simplified_null_return' => false, // disabled by Shift + 'standardize_not_equals' => true, + 'simple_to_complex_string_variable' => true, + 'declare_strict_types' => true, // thinking about it + 'void_return' => false, // thinking about it + 'is_null' => false, // thinking about it + 'strict_comparison' => true, + 'strict_param' => true, // https://mlocati.github.io/php-cs-fixer-configurator/#version:3.2|fixer:strict_param + 'ordered_traits' => true, + + // Disabled for any Global rules + 'phpdoc_no_alias_tag' => false, + 'phpdoc_types_order' => false, + 'phpdoc_tag_type' => false, + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + 'single_line_throw' => false, + 'php_unit_test_case_static_method_calls' => false, + 'php_unit_test_annotation' => false, + 'php_unit_strict' => false, + 'php_unit_set_up_tear_down_visibility' => false, + + // Function/Methods/Const: + 'magic_method_casing' => true, // added from Symfony + 'magic_constant_casing' => true, + 'native_function_casing' => true, + 'no_alias_functions' => true, + 'no_trailing_comma_in_singleline' => true, + 'combine_consecutive_unsets' => true, + 'explicit_indirect_variable' => true, + 'lambda_not_used_import' => true, + + // Unit + 'php_unit_method_casing' => true, + + // Import: + 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], //rewrite psr-12 rule + 'no_unused_imports' => true, + 'fully_qualified_strict_types' => true, + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced', 'strict' => true], + + // Spacing/New Lines: + 'concat_space' => ['spacing' => 'one'], + 'cast_spaces' => false, + 'unary_operator_spaces' => false, + 'linebreak_after_opening_tag' => true, + 'blank_line_after_opening_tag' => true, + 'binary_operator_spaces' => ['default' => 'at_least_single_space', 'operators' => ['=>' => 'align_single_space_minimal']], + 'blank_line_before_statement' => ['statements' => ['return', 'do', 'exit', 'if', 'switch', 'try']], + 'no_extra_blank_lines' => ['tokens' => ['extra', 'throw', 'use']], + 'class_attributes_separation' => ['elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'none']], + 'type_declaration_spaces' => true, + 'include' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_leading_namespace_whitespace' => true, + 'no_multiline_whitespace_around_double_arrow' => true, + 'multiline_whitespace_before_semicolons' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_spaces_around_offset' => true, + 'not_operator_with_successor_space' => false, + 'object_operator_without_whitespace' => true, + 'space_after_semicolon' => true, + 'method_argument_space' => true, + 'return_assignment' => true, + 'single_space_around_construct' => true, + + // Array: + 'array_syntax' => ['syntax' => 'short'], + 'trim_array_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'no_whitespace_before_comma_in_array' => true, + 'normalize_index_brace' => true, + 'trailing_comma_in_multiline' => true, + 'array_indentation' => true, + + // Comments: + 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], + 'single_line_comment_style' => ['comment_types' => ['hash']], + + // PhpDocs: + 'phpdoc_to_comment' => false, + 'phpdoc_indent' => true, + 'no_empty_phpdoc' => true, + 'phpdoc_no_access' => true, + 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_summary' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_var_without_name' => true, + 'phpdoc_separation' => true, + 'phpdoc_align' => ['align' => 'vertical', 'tags' => ['param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var', 'method']], + 'no_superfluous_phpdoc_tags' => true, // thinking about it + ])->setFinder($finder); diff --git a/composer-unused.php b/composer-unused.php new file mode 100644 index 000000000..a049f11e7 --- /dev/null +++ b/composer-unused.php @@ -0,0 +1,13 @@ +addNamedFilter(NamedFilter::fromString('apiato/container-installer')); + + return $config; +}; diff --git a/composer.json b/composer.json index de6a0cbd3..2daee8278 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,8 @@ { "name": "apiato/core", "description": "Core package for Apiato.", + "license": "MIT", + "type": "library", "keywords": [ "apiato", "apiato core", @@ -8,12 +10,6 @@ "Porto", "Porto SAP" ], - "license": "MIT", - "homepage": "https://apiato.io/", - "support": { - "issues": "https://github.com/apiato/core/issues", - "source": "https://github.com/apiato/core" - }, "authors": [ { "name": "Mahmoud Zalt", @@ -27,43 +23,56 @@ "role": "Developer" } ], + "homepage": "https://apiato.io/", + "support": { + "issues": "https://github.com/apiato/core/issues", + "source": "https://github.com/apiato/core" + }, "require": { "php": "^8.2", "apiato/container-installer": "^2.0.8", "composer/class-map-generator": "^1.5", - "laravel/framework": "^11.23|^12.0", + "laravel/framework": "^11.23 || ^12.0", "laravel/tinker": "^2.0", "prettus/l5-repository": "^2.9.1", "spatie/laravel-fractal": "^6.3.1", "thecodingmachine/safe": "^3.0", - "vinkla/hashids": "^12.0|^13.0", + "vinkla/hashids": "^12.0 || ^13.0", "webmozart/assert": "^1.11" }, "require-dev": { - "fakerphp/faker": "^1.19.1", - "friendsofphp/php-cs-fixer": "^3.0", + "driftingly/rector-laravel": "^2.0", + "fakerphp/faker": "^1.24", + "friendsofphp/php-cs-fixer": "^3.8.0", + "icanhazstring/composer-unused": "^0.9.3", "jetbrains/phpstorm-attributes": "^1.0", "larastan/larastan": "^3.0", - "mockery/mockery": "^1.4.4", - "nunomaduro/collision": "^8.0", + "mockery/mockery": "^1.6", + "nunomaduro/collision": "^8.5", "orchestra/testbench": "^9.0|^10.0", "pestphp/pest": "^3.7", "pestphp/pest-plugin-faker": "^3.0", "pestphp/pest-plugin-type-coverage": "^3.2", "phpmd/phpmd": "@stable", + "phpstan/extension-installer": "^1.4", "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "rector/rector": "^2.0", "roave/security-advisories": "dev-latest", + "slevomat/coding-standard": "^8.15", + "squizlabs/php_codesniffer": "^3.12", "thecodingmachine/phpstan-safe-rule": "^1.3" }, + "minimum-stability": "stable", + "prefer-stable": true, "autoload": { "psr-4": { "Apiato\\": "src/" }, "files": [ - "src/Foundation/helpers.php" + "src/Foundation/helpers.php" ] }, "autoload-dev": { @@ -72,6 +81,20 @@ "Workbench\\App\\": "workbench/app/" } }, + "config": { + "allow-plugins": { + "apiato/container-installer": true, + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "pestphp/pest-plugin": true, + "php-http/discovery": true, + "phpstan/extension-installer": true, + "wikimedia/composer-merge-plugin": true + }, + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, "extra": { "laravel": { "providers": [ @@ -80,22 +103,22 @@ } }, "scripts": { - "artisan": [ - "@php vendor/bin/testbench" - ], - "fixer": "php-cs-fixer fix --config=./.php-cs-fixer.dist.php", - "phpstan": [ - "./vendor/bin/phpstan analyse" - ], "post-autoload-dump": [ "@clear", "@prepare" ], + "artisan": [ + "@php vendor/bin/testbench" + ], "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", - "prepare": "@php vendor/bin/testbench package:discover --ansi", + "fixer": "php-cs-fixer fix --config=./.php-cs-fixer.dist.php", "lint": [ "@php vendor/bin/phpstan analyse --verbose --ansi" ], + "phpstan": [ + "./vendor/bin/phpstan analyse" + ], + "prepare": "@php vendor/bin/testbench package:discover --ansi", "test": [ "@clear", "@php vendor/bin/pest" @@ -103,19 +126,5 @@ }, "scripts-descriptions": { "phpstan": "Run PHPStan static analysis against your application." - }, - "config": { - "optimize-autoloader": true, - "preferred-install": "dist", - "sort-packages": true, - "allow-plugins": { - "apiato/container-installer": true, - "composer/package-versions-deprecated": true, - "wikimedia/composer-merge-plugin": true, - "pestphp/pest-plugin": true, - "php-http/discovery": true - } - }, - "minimum-stability": "stable", - "prefer-stable": true + } } diff --git a/config/apiato.php b/config/apiato.php index 14022ac04..f5b851403 100644 --- a/config/apiato.php +++ b/config/apiato.php @@ -1,5 +1,9 @@ [ 'web' => [ - 'class' => \App\Ship\Apps\Web::class, + 'class' => Web::class, 'url' => env('FRONTEND_URL', env('APP_URL', 'http://localhost:3000')), ], ], diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 000000000..41ad3bc52 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,786 @@ + + + PHP Code Style + + + + + + + + + + + + + + config + src + tests + workbench + + + + vendor + workbench/bootstrap + *\.blade\.php$ + + + + + + + + + + warning + + + + + + + + + + + + + + + warning + + + warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + workbench/app/Containers/*/*/Data/Seeders/*_\d+.php + + + + + workbench/app/Containers/*/*/Data/Seeders/*_\d+.php + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Please review this TODO comment: %s + warning + + + + Please review this FIXME comment: %s + warning + + + + + + + + + + + + + + + + + + + + warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + warning + + + + + + + + + tests/*.php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + warning + + + + + + warning + + + + warning + + + + + + + + warning + + + + + + warning + + + + + + + warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpmd.baseline.xml b/phpmd.baseline.xml new file mode 100644 index 000000000..676452fb7 --- /dev/null +++ b/phpmd.baseline.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpmd.ruleset.xml b/phpmd.ruleset.xml new file mode 100644 index 000000000..da23e5348 --- /dev/null +++ b/phpmd.ruleset.xml @@ -0,0 +1,94 @@ + + + + + Apiato PHPMD rulesets + + + phpstan_cache/* + vendor/* + + + + + + + + + + + + + 2 + + + + + 3 + + + + + 2 + + + + + + + 2 + + + + + 2 + + + + + + + + 1 + + + + + 1 + + + + + 2 + + + + + + + + 1 + + + + + 1 + + + + + + + + + + + + + + + diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..661b6a8c5 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,4177 @@ +parameters: + ignoreErrors: + - + message: '#^Call to an undefined method PhpCsFixer\\ConfigInterface\:\:setParallelConfig\(\)\.$#' + identifier: method.notFound + count: 1 + path: .php-cs-fixer.dist.php + + - + message: '#^Cannot call method setFinder\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: .php-cs-fixer.dist.php + + - + message: '#^Cannot call method setRules\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: .php-cs-fixer.dist.php + + - + message: '#^Called ''env'' outside of the config directory which returns null when the config is cached, use ''config''\.$#' + identifier: larastan.noEnvCallsOutsideOfConfig + count: 8 + path: config/apiato.php + + - + message: '#^Class App\\Ship\\Apps\\Web not found\.$#' + identifier: class.notFound + count: 1 + path: config/apiato.php + + - + message: '#^PHPDoc type string of property Apiato\\Console\\Commands\\ListActions\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Console/Commands/ListActions.php + + - + message: '#^PHPDoc type string of property Apiato\\Console\\Commands\\ListTasks\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Console/Commands/ListTasks.php + + - + message: '#^Call to an undefined method Apiato\\Core\\Actions\\Action\:\:run\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Core/Actions/Action.php + + - + message: '#^Method Apiato\\Core\\Actions\\Action\:\:transactionalRun\(\) has parameter \$args with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/Core/Actions/Action.php + + - + message: '#^Dynamic call to static method Illuminate\\Database\\Eloquent\\Builder\\:\:first\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: src/Core/Models/BaseModel.php + + - + message: '#^Method Apiato\\Core\\Models\\BaseModel\:\:getHashedKey\(\) should return int\|string\|null but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Core/Models/BaseModel.php + + - + message: '#^Method Apiato\\Core\\Models\\BaseModel\:\:newFactory\(\) has no return type specified\.$#' + identifier: missingType.return + count: 1 + path: src/Core/Models/BaseModel.php + + - + message: '#^Only booleans are allowed in &&, mixed given on the left side\.$#' + identifier: booleanAnd.leftNotBoolean + count: 1 + path: src/Core/Models/BaseModel.php + + - + message: '#^Only booleans are allowed in an if condition, mixed given\.$#' + identifier: if.condNotBoolean + count: 1 + path: src/Core/Models/BaseModel.php + + - + message: '#^Out of 286 possible return types, only 267 \- 93\.3 %% actually have it\. Add more return types to get over 99 %%$#' + identifier: typeCoverage.returnTypeCoverage + count: 3 + path: src/Core/Models/BaseModel.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 6 + path: src/Core/Models/BaseModel.php + + - + message: '#^Parameter \#1 \$hash of method Apiato\\Support\\HashidsManagerDecorator\:\:decode\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Models/BaseModel.php + + - + message: '#^Trait Apiato\\Core\\Models\\Concerns\\HandlesFactoryDiscovery uses generic trait Illuminate\\Database\\Eloquent\\Factories\\HasFactory but does not specify its types\: TFactory$#' + identifier: missingType.generics + count: 1 + path: src/Core/Models/BaseModel.php + + - + message: '#^Call to function is_callable\(\) with Closure will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Dead catch \- Illuminate\\Database\\Eloquent\\ModelNotFoundException is never thrown in the try block\.$#' + identifier: catch.neverThrown + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Dynamic call to static method Illuminate\\Database\\Eloquent\\Model\:\:with\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Expression on left side of \?\? is not nullable\.$#' + identifier: nullCoalesce.expr + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:all\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:all\(\) should return Illuminate\\Database\\Eloquent\\Collection\<\(int\|string\), TModel of Illuminate\\Database\\Eloquent\\Model\> but returns mixed\.$#' + identifier: return.type + count: 2 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:arrayToSearchQuery\(\) has parameter \$decodedSearchArray with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:create\(\) has parameter \$attributes with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:create\(\) should return TModel of Illuminate\\Database\\Eloquent\\Model but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:find\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:find\(\) has parameter \$id with generic interface Illuminate\\Contracts\\Support\\Arrayable but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:find\(\) has parameter \$id with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:find\(\) return type with generic interface Illuminate\\Contracts\\Support\\Arrayable does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:find\(\) should return Illuminate\\Database\\Eloquent\\Collection\<\(int\|string\), TModel of Illuminate\\Database\\Eloquent\\Model\>\|\(TModel of Illuminate\\Database\\Eloquent\\Model\)\|null but returns mixed\.$#' + identifier: return.type + count: 2 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findByField\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findByField\(\) has parameter \$field with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findByField\(\) has parameter \$value with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findByField\(\) should return Illuminate\\Database\\Eloquent\\Collection\<\(int\|string\), TModel of Illuminate\\Database\\Eloquent\\Model\> but returns mixed\.$#' + identifier: return.type + count: 2 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findById\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findMany\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findMany\(\) has parameter \$ids with generic interface Illuminate\\Contracts\\Support\\Arrayable but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findMany\(\) has parameter \$ids with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findOrFail\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findWhere\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findWhere\(\) has parameter \$where with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:findWhere\(\) should return Illuminate\\Database\\Eloquent\\Collection\<\(int\|string\), TModel of Illuminate\\Database\\Eloquent\\Model\> but returns mixed\.$#' + identifier: return.type + count: 2 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:firstOrCreate\(\) has parameter \$attributes with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:firstOrCreate\(\) has parameter \$values with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:getDecodedSearchValues\(\) has parameter \$searchData with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:getDecodedSearchValues\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:getModel\(\) should return TModel of Illuminate\\Database\\Eloquent\\Model but returns Illuminate\\Database\\Eloquent\\Model\.$#' + identifier: return.type + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:make\(\) has parameter \$attributes with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:paginate\(\) has parameter \$columns with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:parserSearchData\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:store\(\) has parameter \$data with generic interface Illuminate\\Contracts\\Support\\Arrayable but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:store\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:update\(\) has Apiato\\Core\\Repositories\\Exceptions\\ResourceNotFound in PHPDoc @throws tag but it''s not thrown\.$#' + identifier: throws.unusedType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:update\(\) has parameter \$attributes with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Repositories\\Repository\:\:update\(\) should return TModel of Illuminate\\Database\\Eloquent\\Model but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Only booleans are allowed in &&, mixed given on the left side\.$#' + identifier: booleanAnd.leftNotBoolean + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Only booleans are allowed in &&, mixed given on the right side\.$#' + identifier: booleanAnd.rightNotBoolean + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Out of 286 possible return types, only 267 \- 93\.3 %% actually have it\. Add more return types to get over 99 %%$#' + identifier: typeCoverage.returnTypeCoverage + count: 14 + path: src/Core/Repositories/Repository.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 13 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$attributes of method Illuminate\\Database\\Eloquent\\Model\:\:update\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$field \(array\|\(Closure\(static\)\: mixed\)\|Illuminate\\Contracts\\Database\\Query\\Expression\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:findByField\(\) should be contravariant with parameter \$field \(mixed\) of method Prettus\\Repository\\Contracts\\RepositoryInterface\:\:findByField\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$field \(array\|\(Closure\(static\)\: mixed\)\|Illuminate\\Contracts\\Database\\Query\\Expression\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:findByField\(\) should be contravariant with parameter \$field \(mixed\) of method Prettus\\Repository\\Eloquent\\BaseRepository\:\:findByField\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$hash of method Apiato\\Support\\HashidsManagerDecorator\:\:decode\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$id \(array\|Illuminate\\Contracts\\Support\\Arrayable\|int\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:find\(\) should be contravariant with parameter \$id \(mixed\) of method Prettus\\Repository\\Contracts\\RepositoryInterface\:\:find\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$id \(array\|Illuminate\\Contracts\\Support\\Arrayable\|int\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:find\(\) should be contravariant with parameter \$id \(mixed\) of method Prettus\\Repository\\Eloquent\\BaseRepository\:\:find\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$id \(int\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:delete\(\) should be contravariant with parameter \$id \(mixed\) of method Prettus\\Repository\\Contracts\\RepositoryInterface\:\:delete\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$id \(int\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:delete\(\) should be contravariant with parameter \$id \(mixed\) of method Prettus\\Repository\\Eloquent\\BaseRepository\:\:delete\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#1 \$key of method Illuminate\\Http\\Request\:\:filled\(\) expects array\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#2 \$columns of method Prettus\\Repository\\Eloquent\\BaseRepository\:\:findWhere\(\) expects array, array\|string given\.$#' + identifier: argument.type + count: 2 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#2 \$id \(int\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:update\(\) should be contravariant with parameter \$id \(mixed\) of method Prettus\\Repository\\Contracts\\RepositoryInterface\:\:update\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#2 \$id \(int\|string\) of method Apiato\\Core\\Repositories\\Repository\:\:update\(\) should be contravariant with parameter \$id \(mixed\) of method Prettus\\Repository\\Eloquent\\BaseRepository\:\:update\(\)$#' + identifier: method.childParameterType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Parameter \#3 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Property Prettus\\Repository\\Eloquent\\BaseRepository\:\:\$model \(Illuminate\\Database\\Eloquent\\Model\) does not accept mixed\.$#' + identifier: assign.propertyType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Return type \(bool\) of method Apiato\\Core\\Repositories\\Repository\:\:delete\(\) should be compatible with return type \(int\) of method Prettus\\Repository\\Contracts\\RepositoryInterface\:\:delete\(\)$#' + identifier: method.childReturnType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Return type \(bool\) of method Apiato\\Core\\Repositories\\Repository\:\:delete\(\) should be compatible with return type \(int\) of method Prettus\\Repository\\Eloquent\\BaseRepository\:\:delete\(\)$#' + identifier: method.childReturnType + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Http\\Request\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: src/Core/Repositories/Repository.php + + - + message: '#^Method Apiato\\Core\\Requests\\Request\:\:route\(\) should return object\|string\|null but returns array\\|int\.$#' + identifier: return.type + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Method Apiato\\Core\\Requests\\Request\:\:route\(\) should return object\|string\|null but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Only booleans are allowed in &&, mixed given on the right side\.$#' + identifier: booleanAnd.rightNotBoolean + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Only booleans are allowed in a negated boolean, mixed given\.$#' + identifier: booleanNot.exprNotBoolean + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Out of 286 possible return types, only 267 \- 93\.3 %% actually have it\. Add more return types to get over 99 %%$#' + identifier: typeCoverage.returnTypeCoverage + count: 2 + path: src/Core/Requests/Request.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 4 + path: src/Core/Requests/Request.php + + - + message: '#^Parameter \#1 \$array of static method Illuminate\\Support\\Arr\:\:dot\(\) expects iterable, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Parameter \#1 \$array of static method Illuminate\\Support\\Arr\:\:set\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Parameter \#1 \.\.\.\$hash of method Apiato\\Support\\HashidsManagerDecorator\:\:decodeOrFail\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Parameter \#1 \.\.\.\$hash of method Apiato\\Support\\HashidsManagerDecorator\:\:decodeOrFail\(\) expects string, object\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Core/Requests/Request.php + + - + message: '#^Out of 9 possible constant types, only 0 \- 0\.0 %% actually have it\. Add more constant types to get over 99 %%$#' + identifier: typeCoverage.constantTypeCoverage + count: 1 + path: src/Core/Seeders/Seeder.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 4 + path: src/Core/Transformers/Transformer.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:commands\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:configs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:events\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:factory\(\) return type with generic class Apiato\\Foundation\\Configuration\\Factory does not specify its types\: TModel, TFactory$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:helpers\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:inferBasePath\(\) should return string but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:migrations\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:provider\(\) return type with generic class Apiato\\Foundation\\Configuration\\Provider does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:providers\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:repository\(\) return type with generic class Apiato\\Foundation\\Configuration\\Repository does not specify its types\: TModel, TRepository$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:seeding\(\) return type with generic class Apiato\\Foundation\\Configuration\\Seeding does not specify its types\: TSeeder$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Apiato\:\:webRoutes\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 2 + path: src/Foundation/Apiato.php + + - + message: '#^Property Apiato\\Foundation\\Apiato\:\:\$factory with generic class Apiato\\Foundation\\Configuration\\Factory does not specify its types\: TModel, TFactory$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Property Apiato\\Foundation\\Apiato\:\:\$provider with generic class Apiato\\Foundation\\Configuration\\Provider does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Property Apiato\\Foundation\\Apiato\:\:\$repository with generic class Apiato\\Foundation\\Configuration\\Repository does not specify its types\: TModel, TRepository$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Property Apiato\\Foundation\\Apiato\:\:\$seeding with generic class Apiato\\Foundation\\Configuration\\Seeding does not specify its types\: TSeeder$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Apiato.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\ApplicationBuilder\:\:getDirs\(\) should return array\ but returns list\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/ApplicationBuilder.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Factory\:\:resolveFactoryName\(\) should return class\-string\\|null but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/Factory.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Factory\:\:resolveFactoryNameUsing\(\) return type with generic class Apiato\\Foundation\\Configuration\\Factory does not specify its types\: TModel, TFactory$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Configuration/Factory.php + + - + message: '#^Parameter \#1 \$callback of method Apiato\\Foundation\\Configuration\\Factory\\:\:resolveFactoryNameUsing\(\) expects Closure\(class\-string\\)\: \(class\-string\\|null\), Closure\(string\)\: \(class\-string\|null\) given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Configuration/Factory.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Localization\:\:buildNamespaceFor\(\) should return string but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/Localization.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Provider\:\:loadFrom\(\) return type with generic class Apiato\\Foundation\\Configuration\\Provider does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Configuration/Provider.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Provider\:\:toArray\(\) should return array\\> but returns array\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/Provider.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Repository\:\:resolveModelName\(\) should return class\-string\ but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/Repository.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Repository\:\:resolveModelNameUsing\(\) return type with generic class Apiato\\Foundation\\Configuration\\Repository does not specify its types\: TModel, TRepository$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Configuration/Repository.php + + - + message: '#^Parameter \#1 \$callback of method Apiato\\Foundation\\Configuration\\Repository\\:\:resolveModelNameUsing\(\) expects Closure\(class\-string\\)\: class\-string\, Closure\(string\)\: class\-string given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Configuration/Repository.php + + - + message: '#^Binary operation "\." between ''throttle\:'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Foundation/Configuration/Routing.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Routing\:\:resolveApiVersionFor\(\) should return string but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/Routing.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Routing\:\:webRoutes\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/Routing.php + + - + message: '#^Only booleans are allowed in an if condition, mixed given\.$#' + identifier: if.condNotBoolean + count: 1 + path: src/Foundation/Configuration/Routing.php + + - + message: '#^Parameter \#1 \$value of method Illuminate\\Routing\\RouteRegistrar\:\:domain\(\) expects BackedEnum\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Configuration/Routing.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Seeding\:\:getSortedFiles\(\) should return array\\> but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/Seeding.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Seeding\:\:loadFrom\(\) return type with generic class Apiato\\Foundation\\Configuration\\Seeding does not specify its types\: TSeeder$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Configuration/Seeding.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\Seeding\:\:sortUsing\(\) return type with generic class Apiato\\Foundation\\Configuration\\Seeding does not specify its types\: TSeeder$#' + identifier: missingType.generics + count: 1 + path: src/Foundation/Configuration/Seeding.php + + - + message: '#^Only numeric types are allowed in \+, int\<0, max\>\|false given on the left side\.$#' + identifier: plus.leftNonNumeric + count: 1 + path: src/Foundation/Configuration/Seeding.php + + - + message: '#^Parameter \#1 \$callback of method Apiato\\Foundation\\Configuration\\Seeding\\:\:sortUsing\(\) expects Closure\(array\, non\-empty\-string\>\>\)\: class\-string\, Closure\(array\)\: array\ given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Configuration/Seeding.php + + - + message: '#^Parameter \#1 \$classMapGroupedByDirectory of method Apiato\\Foundation\\Configuration\\Seeding\\:\:getSortedFiles\(\) expects array\, non\-empty\-string\>\>, list\\> given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Configuration/Seeding.php + + - + message: '#^Method Apiato\\Foundation\\Configuration\\View\:\:buildNamespaceFor\(\) should return string but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Foundation/Configuration/View.php + + - + message: '#^PHPDoc tag @var with type Apiato\\Core\\Seeders\\Seeder is not subtype of native type \$this\(Apiato\\Foundation\\Database\\DatabaseSeeder\)\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Foundation/Database/DatabaseSeeder.php + + - + message: '#^Parameter \#1 \$callback of static method Illuminate\\Database\\Connection\:\:transaction\(\) expects Closure\(Illuminate\\Database\\Connection\)\: Apiato\\Core\\Seeders\\Seeder, Closure\(string\)\: Apiato\\Core\\Seeders\\Seeder given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Database/DatabaseSeeder.php + + - + message: '#^Variable \$class in PHPDoc tag @var does not exist\.$#' + identifier: varTag.variableNotFound + count: 1 + path: src/Foundation/Database/DatabaseSeeder.php + + - + message: '#^Parameter \#1 \$path of method Illuminate\\Support\\ServiceProvider\:\:mergeConfigFrom\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Support/Providers/ConfigServiceProvider.php + + - + message: '#^Parameter \#1 \$string of static method Illuminate\\Support\\Str\:\:of\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Support/Providers/ConfigServiceProvider.php + + - + message: '#^Only booleans are allowed in an if condition, mixed given\.$#' + identifier: if.condNotBoolean + count: 1 + path: src/Foundation/Support/Providers/RateLimitingServiceProvider.php + + - + message: '#^Parameter \#1 \$decayMinutes of static method Illuminate\\Cache\\RateLimiting\\Limit\:\:perMinutes\(\) expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Support/Providers/RateLimitingServiceProvider.php + + - + message: '#^Parameter \#1 \$name of static method Illuminate\\Support\\Facades\\RateLimiter\:\:for\(\) expects string\|UnitEnum, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Support/Providers/RateLimitingServiceProvider.php + + - + message: '#^Parameter \#2 \$maxAttempts of static method Illuminate\\Cache\\RateLimiting\\Limit\:\:perMinutes\(\) expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Foundation/Support/Providers/RateLimitingServiceProvider.php + + - + message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#' + identifier: ternary.shortNotAllowed + count: 1 + path: src/Foundation/Support/Providers/RateLimitingServiceProvider.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ActionGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ActionGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ActionGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ActionGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ActionGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ActionGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Pluralizer\:\:plural\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ActionGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ActionGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:upper\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ActionGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ActionGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ActionGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ConfigurationGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ConfigurationGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ConfigurationGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ConfigurationGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ConfigurationGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ConfigurationGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ConfigurationGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ConfigurationGenerator.php + + - + message: '#^Binary operation "\." between ''Create'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 7 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between ''Delete'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 7 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between ''Find'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 7 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between ''List'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 7 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between ''Update'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 7 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''Created'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''Deleted'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''Factory'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''FactoryTest'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''Listed'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''MigrationTest'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''Requested'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''Transformer'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''Updated'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerApiGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerApiGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerApiGenerator\:\:runCallParam\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Only booleans are allowed in &&, array\|bool\|string\|null given on the left side\.$#' + identifier: booleanAnd.leftNotBoolean + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Only booleans are allowed in &&, array\|bool\|string\|null given on the right side\.$#' + identifier: booleanAnd.rightNotBoolean + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Only booleans are allowed in a ternary operator condition, array\|bool\|string\|null given\.$#' + identifier: ternary.condNotBoolean + count: 2 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Only booleans are allowed in an if condition, array\|bool\|string\|null given\.$#' + identifier: if.condNotBoolean + count: 3 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ContainerApiGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Pluralizer\:\:plural\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:kebab\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ContainerApiGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#' + identifier: ternary.shortNotAllowed + count: 2 + path: src/Generator/Commands/ContainerApiGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ContainerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerGenerator.php + + - + message: '#^Only booleans are allowed in an if condition, array\|bool\|string\|null given\.$#' + identifier: if.condNotBoolean + count: 2 + path: src/Generator/Commands/ContainerGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ContainerGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ContainerGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ContainerGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerGenerator.php + + - + message: '#^Binary operation "\." between ''Create'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 5 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Binary operation "\." between ''Delete'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 5 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Binary operation "\." between ''Edit'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 3 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Binary operation "\." between ''Find'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 5 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Binary operation "\." between ''List'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 5 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Binary operation "\." between ''Store'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 3 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Binary operation "\." between ''Update'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 5 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerWebGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerWebGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ContainerWebGenerator\:\:runCallParam\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ContainerWebGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:kebab\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ContainerWebGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ContainerWebGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ControllerGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ControllerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ControllerGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ControllerGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ControllerGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ControllerGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Pluralizer\:\:plural\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ControllerGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:camel\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ControllerGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 2 + path: src/Generator/Commands/ControllerGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ControllerGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ControllerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\EventGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\EventGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^Only booleans are allowed in an if condition, array\|bool\|string\|null given\.$#' + identifier: if.condNotBoolean + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\EventGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\EventGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#' + identifier: ternary.shortNotAllowed + count: 1 + path: src/Generator/Commands/EventGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\EventListenerGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/EventListenerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\EventListenerGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/EventListenerGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\EventListenerGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/EventListenerGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\EventListenerGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/EventListenerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ExceptionGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ExceptionGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ExceptionGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ExceptionGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ExceptionGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ExceptionGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ExceptionGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ExceptionGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\FunctionalTestGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/FunctionalTestGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\FunctionalTestGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/FunctionalTestGenerator.php + + - + message: '#^Only booleans are allowed in a ternary operator condition, string\|null given\.$#' + identifier: ternary.condNotBoolean + count: 1 + path: src/Generator/Commands/FunctionalTestGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\FunctionalTestGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/FunctionalTestGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/FunctionalTestGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\FunctionalTestGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/FunctionalTestGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\JobGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/JobGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\JobGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/JobGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\JobGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/JobGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\JobGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/JobGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\MailGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/MailGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\MailGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/MailGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\MailGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/MailGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\MailGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/MailGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\MiddlewareGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/MiddlewareGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\MiddlewareGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/MiddlewareGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\MiddlewareGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/MiddlewareGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\MiddlewareGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/MiddlewareGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\MigrationGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/MigrationGenerator.php + + - + message: '#^Out of 9 possible constant types, only 0 \- 0\.0 %% actually have it\. Add more constant types to get over 99 %%$#' + identifier: typeCoverage.constantTypeCoverage + count: 1 + path: src/Generator/Commands/MigrationGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\MigrationGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/MigrationGenerator.php + + - + message: '#^Parameter \#1 \$path of method Apiato\\Generator\\Generator\:\:getFilePath\(\) expects string, array\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/MigrationGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/MigrationGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\MigrationGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/MigrationGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ModelFactoryGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ModelFactoryGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ModelFactoryGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ModelFactoryGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ModelFactoryGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ModelFactoryGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/ModelFactoryGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ModelFactoryGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ModelFactoryGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ModelGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ModelGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ModelGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ModelGenerator.php + + - + message: '#^Only booleans are allowed in an if condition, array\|bool\|string\|null given\.$#' + identifier: if.condNotBoolean + count: 1 + path: src/Generator/Commands/ModelGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ModelGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ModelGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ModelGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ModelGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\NotificationGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/NotificationGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\NotificationGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/NotificationGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\NotificationGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/NotificationGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\NotificationGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/NotificationGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\PolicyGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/PolicyGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\PolicyGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/PolicyGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\PolicyGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/PolicyGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\PolicyGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/PolicyGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ReadmeGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ReadmeGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ReadmeGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ReadmeGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ReadmeGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ReadmeGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ReadmeGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ReadmeGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\RepositoryGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/RepositoryGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\RepositoryGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/RepositoryGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\RepositoryGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/RepositoryGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RepositoryGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\RepositoryGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/RepositoryGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\RequestGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/RequestGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\RequestGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/RequestGenerator.php + + - + message: '#^Only booleans are allowed in a ternary operator condition, string\|null given\.$#' + identifier: ternary.condNotBoolean + count: 1 + path: src/Generator/Commands/RequestGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\RequestGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/RequestGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RequestGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\RequestGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/RequestGenerator.php + + - + message: '#^Binary operation "\." between ''/v'' and array\|int\|string results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Binary operation "\." between ''v'' and array\|int\|string results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''/'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\RouteGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\RouteGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Only booleans are allowed in an if condition, array\|bool\|string\|null given\.$#' + identifier: if.condNotBoolean + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\RouteGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:headline\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:snake\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:studly\(\) expects string, string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:upper\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\RouteGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/RouteGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\SeederGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/SeederGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\SeederGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/SeederGenerator.php + + - + message: '#^Out of 9 possible constant types, only 0 \- 0\.0 %% actually have it\. Add more constant types to get over 99 %%$#' + identifier: typeCoverage.constantTypeCoverage + count: 1 + path: src/Generator/Commands/SeederGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\SeederGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/SeederGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\SeederGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/SeederGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ServiceProviderGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ServiceProviderGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ServiceProviderGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ServiceProviderGenerator.php + + - + message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#' + identifier: booleanNot.exprNotBoolean + count: 1 + path: src/Generator/Commands/ServiceProviderGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ServiceProviderGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ServiceProviderGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ServiceProviderGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ServiceProviderGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\SubActionGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/SubActionGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\SubActionGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/SubActionGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\SubActionGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/SubActionGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\SubActionGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/SubActionGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\TaskGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\TaskGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Only booleans are allowed in a ternary operator condition, string\|null given\.$#' + identifier: ternary.condNotBoolean + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\TaskGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Pluralizer\:\:plural\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:camel\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\TaskGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/TaskGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\TestCaseGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/TestCaseGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\TestCaseGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/TestCaseGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\TestCaseGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/TestCaseGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TestCaseGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\TestCaseGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/TestCaseGenerator.php + + - + message: '#^Binary operation "\." between non\-falsy\-string and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Call to an undefined method object\:\:getHidden\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Call to an undefined method object\:\:getTable\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Call to function in_array\(\) requires parameter \#3 to be true\.$#' + identifier: function.strict + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\TransformerGenerator\:\:getEndOfLine\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\TransformerGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\TransformerGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\TransformerGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Parameter \#1 \$table of static method Illuminate\\Support\\Facades\\Schema\:\:getColumnListing\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:lower\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Parameter \#2 \$haystack of function in_array expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Parameter \#2 \$model of method Apiato\\Generator\\Commands\\TransformerGenerator\:\:getListOfAllAttributes\(\) expects string, array\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\TransformerGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/TransformerGenerator.php + + - + message: '#^Binary operation "\." between ''\{section\-name\}/…'' and array\|int\|non\-empty\-string results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^Binary operation "\." between mixed and ''/\*'' results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\UnitTestGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\UnitTestGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^Only booleans are allowed in an if condition, string\|null given\.$#' + identifier: if.condNotBoolean + count: 3 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\UnitTestGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^Parameter \#1 \$value of static method Illuminate\\Support\\Str\:\:studly\(\) expects string, string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\UnitTestGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/UnitTestGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ValueGenerator\:\:getUserInputs\(\) never returns null so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Generator/Commands/ValueGenerator.php + + - + message: '#^Method Apiato\\Generator\\Commands\\ValueGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ValueGenerator.php + + - + message: '#^PHPDoc type string of property Apiato\\Generator\\Commands\\ValueGenerator\:\:\$description is not the same as PHPDoc type string\|null of overridden property Illuminate\\Console\\Command\:\:\$description\.$#' + identifier: property.phpDocType + count: 1 + path: src/Generator/Commands/ValueGenerator.php + + - + message: '#^Property Apiato\\Generator\\Commands\\ValueGenerator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Commands/ValueGenerator.php + + - + message: '#^Access to an undefined property \$this\(Apiato\\Generator\\Generator\)&Apiato\\Generator\\Interfaces\\ComponentsGenerator\:\:\$nameStructure\.$#' + identifier: property.notFound + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Access to an undefined property \$this\(Apiato\\Generator\\Generator\)&Apiato\\Generator\\Interfaces\\ComponentsGenerator\:\:\$pathStructure\.$#' + identifier: property.notFound + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Cannot cast array\|int\|string to string\.$#' + identifier: cast.string + count: 2 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:checkParameterOrAsk\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:checkParameterOrAsk\(\) should return array\|int\|string but returns array\|bool\|string\.$#' + identifier: return.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:checkParameterOrChoice\(\) has parameter \$choices with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:checkParameterOrChoice\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:checkParameterOrChoice\(\) should return array\|bool\|string\|null but returns int\|string\.$#' + identifier: return.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:checkParameterOrConfirm\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:getInput\(\) has parameter \$arg with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:getInput\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:getInput\(\) should return array\|string\|null but returns array\|bool\|string\|null\.$#' + identifier: return.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:getOptions\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:parseFileStructure\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:parsePathStructure\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:parsePathStructure\(\) has parameter \$path with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:parsePathStructure\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:parseStubContent\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:parseStubContent\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:sanitizeUserData\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\Generator\:\:sanitizeUserData\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Out of 9 possible constant types, only 0 \- 0\.0 %% actually have it\. Add more constant types to get over 99 %%$#' + identifier: typeCoverage.constantTypeCoverage + count: 4 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#1 \$filename of method Apiato\\Generator\\Generator\:\:parseFileStructure\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#1 \$key of method Illuminate\\Console\\Command\:\:argument\(\) expects string\|null, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#1 \$path of method Apiato\\Generator\\Generator\:\:getFilePath\(\) expects string, array\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#1 \$path of method Apiato\\Generator\\Generator\:\:parsePathStructure\(\) expects array\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#1 \$string of function trim expects string, array\|bool\|string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#1 \$string of function ucfirst expects string, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#2 \$data of method Apiato\\Generator\\Generator\:\:parseFileStructure\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#2 \$data of method Apiato\\Generator\\Generator\:\:parsePathStructure\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#2 \$data of method Apiato\\Generator\\Generator\:\:parseStubContent\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#2 \$options of function Laravel\\Prompts\\select expects array\\|Illuminate\\Support\\Collection\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#2 \$replace of function str_replace expects array\\|string, list given\.$#' + identifier: argument.type + count: 3 + path: src/Generator/Generator.php + + - + message: '#^Parameter \#3 \$subject of function str_replace expects array\\|string, array\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Parameter \$default of function Laravel\\Prompts\\text expects string, int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Property Apiato\\Generator\\Generator\:\:\$defaultInputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Property Apiato\\Generator\\Generator\:\:\$fileName \(string\) does not accept array\|int\|string\.$#' + identifier: assign.propertyType + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Property Apiato\\Generator\\Generator\:\:\$inputs type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Property Apiato\\Generator\\Generator\:\:\$renderedStubContent \(string\) does not accept array\|string\.$#' + identifier: assign.propertyType + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Property Apiato\\Generator\\Generator\:\:\$userData type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Generator.php + + - + message: '#^Method Apiato\\Generator\\GeneratorsServiceProvider\:\:getGeneratorCommands\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/GeneratorsServiceProvider.php + + - + message: '#^Method Apiato\\Generator\\Interfaces\\ComponentsGenerator\:\:getUserInputs\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Generator/Interfaces/ComponentsGenerator.php + + - + message: '#^Only booleans are allowed in a negated boolean, mixed given\.$#' + identifier: booleanNot.exprNotBoolean + count: 1 + path: src/Http/Middleware/ProcessETag.php + + - + message: '#^Only booleans are allowed in &&, mixed given on the right side\.$#' + identifier: booleanAnd.rightNotBoolean + count: 1 + path: src/Http/Middleware/ValidateJsonContent.php + + - + message: '#^Call to function array_filter\(\) requires parameter \#2 to be passed to avoid loose comparison semantics\.$#' + identifier: arrayFilter.strict + count: 1 + path: src/Http/RequestRelation.php + + - + message: '#^Method Apiato\\Http\\RequestRelation\:\:parseIncludes\(\) should return array\ but returns array\.$#' + identifier: return.type + count: 1 + path: src/Http/RequestRelation.php + + - + message: '#^PHPDoc tag @var for variable \$relation contains generic class Illuminate\\Database\\Eloquent\\Relations\\Relation but does not specify its types\: TRelatedModel, TDeclaringModel, TResult$#' + identifier: missingType.generics + count: 1 + path: src/Http/RequestRelation.php + + - + message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Config\\Repository\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: src/Http/RequestRelation.php + + - + message: '#^Variable method call on Illuminate\\Database\\Eloquent\\Model\.$#' + identifier: method.dynamicName + count: 1 + path: src/Http/RequestRelation.php + + - + message: '#^Method Apiato\\Http\\Response\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Http/Response.php + + - + message: '#^Strict comparison using \=\=\= between string and false will always evaluate to false\.$#' + identifier: identical.alwaysFalse + count: 1 + path: src/Http/Response.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 100000 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 50200 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 50300 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 50400 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 50500 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 50600 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 70000 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 70100 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 70200 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 70300 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 70400 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 80000 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 80100 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 80300 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 80400 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:provideMinPhpVersion\(\) never returns 80500 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Out of 9 possible constant types, only 0 \- 0\.0 %% actually have it\. Add more constant types to get over 99 %%$#' + identifier: typeCoverage.constantTypeCoverage + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^PHPDoc tag @implements contains generic type Rector\\VersionBonding\\Contract\\MinPhpVersionInterface\ but interface Rector\\VersionBonding\\Contract\\MinPhpVersionInterface is not generic\.$#' + identifier: generics.notGeneric + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Parameter \#1 \$node \(PhpParser\\Node\\Expr\\MethodCall\) of method Apiato\\Linters\\Rector\\AssertInstanceToStaticCallRector\:\:refactor\(\) should be contravariant with parameter \$node \(PhpParser\\Node\) of method Rector\\Contract\\Rector\\RectorInterface\:\:refactor\(\)$#' + identifier: method.childParameterType + count: 2 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Parameter \#2 \$name of class PhpParser\\Node\\Expr\\StaticCall constructor expects PhpParser\\Node\\Expr\|PhpParser\\Node\\Identifier\|string, string\|null given\.$#' + identifier: argument.type + count: 1 + path: src/Linters/Rector/AssertInstanceToStaticCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 100000 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 50200 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 50300 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 50400 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 50500 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 50600 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 70000 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 70100 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 70200 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 70300 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 70400 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 80000 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 80100 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 80300 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 80400 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:provideMinPhpVersion\(\) never returns 80500 so it can be removed from the return type\.$#' + identifier: return.unusedType + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Out of 9 possible constant types, only 0 \- 0\.0 %% actually have it\. Add more constant types to get over 99 %%$#' + identifier: typeCoverage.constantTypeCoverage + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^PHPDoc tag @implements contains generic type Rector\\VersionBonding\\Contract\\MinPhpVersionInterface\ but interface Rector\\VersionBonding\\Contract\\MinPhpVersionInterface is not generic\.$#' + identifier: generics.notGeneric + count: 1 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Parameter \#1 \$node \(PhpParser\\Node\\Expr\\StaticCall\) of method Apiato\\Linters\\Rector\\MockObjectStaticToInstanceCallRector\:\:refactor\(\) should be contravariant with parameter \$node \(PhpParser\\Node\) of method Rector\\Contract\\Rector\\RectorInterface\:\:refactor\(\)$#' + identifier: method.childParameterType + count: 2 + path: src/Linters/Rector/MockObjectStaticToInstanceCallRector.php + + - + message: '#^Anonymous function should return int but returns array\\|int\.$#' + identifier: return.type + count: 1 + path: src/Macros/MacroServiceProvider.php + + - + message: '#^PHPDoc tag @var for variable \$this contains generic class Illuminate\\Support\\Collection but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Macros/MacroServiceProvider.php + + - + message: '#^PHPDoc tag @var with type Illuminate\\Config\\Repository is not subtype of native type \$this\(Apiato\\Macros\\MacroServiceProvider\)\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Macros/MacroServiceProvider.php + + - + message: '#^Parameter \#1 \$callback of method Illuminate\\Support\\Collection\<\(int\|string\),mixed\>\:\:map\(\) expects callable\(mixed, int\|string\)\: int, Closure\(string\)\: int given\.$#' + identifier: argument.type + count: 1 + path: src/Macros/MacroServiceProvider.php + + - + message: '#^Parameter \#1 \$source of static method Apiato\\Support\\Sanitizer\:\:sanitize\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Macros/MacroServiceProvider.php + + - + message: '#^Parameter \#2 \$fields of static method Apiato\\Support\\Sanitizer\:\:sanitize\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Macros/MacroServiceProvider.php + + - + message: '#^Property Illuminate\\Config\\Repository\:\:\$items \(array\\) does not accept array\.$#' + identifier: assign.propertyType + count: 1 + path: src/Macros/MacroServiceProvider.php + + - + message: '#^Class Apiato\\Support\\Facades\\Response has PHPDoc tag @method for method respond\(\) parameter \#2 \$headers with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Support/Facades/Response.php + + - + message: '#^Method Apiato\\Support\\HashidsManagerDecorator\:\:__call\(\) has no return type specified\.$#' + identifier: missingType.return + count: 1 + path: src/Support/HashidsManagerDecorator.php + + - + message: '#^Method Apiato\\Support\\HashidsManagerDecorator\:\:__call\(\) has parameter \$parameters with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Support/HashidsManagerDecorator.php + + - + message: '#^Method Apiato\\Support\\HashidsManagerDecorator\:\:decode\(\) should return array\\|int\|null but returns array\.$#' + identifier: return.type + count: 1 + path: src/Support/HashidsManagerDecorator.php + + - + message: '#^Method Apiato\\Support\\HashidsManagerDecorator\:\:decodeOrFail\(\) should return array\\|int but returns array\\|int\>\.$#' + identifier: return.type + count: 1 + path: src/Support/HashidsManagerDecorator.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 2 + path: src/Support/HashidsManagerDecorator.php + + - + message: '#^Parameter \#1 \.\.\.\$numbers of method Apiato\\Support\\HashidsManagerDecorator\:\:encode\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Support/HashidsManagerDecorator.php + + - + message: '#^Method Apiato\\Support\\Sanitizer\:\:sanitize\(\) should return array\ but returns array\.$#' + identifier: return.type + count: 1 + path: src/Support/Sanitizer.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:expect\(\)\.$#' + identifier: method.notFound + count: 4 + path: tests/Arch/CoreTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\TestCall\:\:preset\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot access property \$not on mixed\.$#' + identifier: property.nonObject + count: 2 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method classes\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method ignoring\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method php\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method toBeAbstract\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method toBeFinal\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method toOnlyBeUsedIn\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method toUse\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Arch/CoreTest.php + + - + message: '#^Cannot call method toUseStrictEquality\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Arch/CoreTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Console/CommandServiceProviderTest.php + + - + message: '#^Parameter \#1 \$key of method Illuminate\\Support\\Collection\<\(int\|string\),mixed\>\:\:has\(\) expects array\<\(int\|string\)\>\|int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Functional/Console/CommandServiceProviderTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:artisan\(\)\.$#' + identifier: method.notFound + count: 2 + path: tests/Functional/Console/Commands/ListActionsTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Console/Commands/ListActionsTest.php + + - + message: '#^Cannot call method assertExitCode\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Console/Commands/ListActionsTest.php + + - + message: '#^Cannot call method expectsOutput\(\) on mixed\.$#' + identifier: method.nonObject + count: 8 + path: tests/Functional/Console/Commands/ListActionsTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:artisan\(\)\.$#' + identifier: method.notFound + count: 2 + path: tests/Functional/Console/Commands/ListTasksTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Console/Commands/ListTasksTest.php + + - + message: '#^Cannot call method assertExitCode\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Console/Commands/ListTasksTest.php + + - + message: '#^Cannot call method expectsOutput\(\) on mixed\.$#' + identifier: method.nonObject + count: 4 + path: tests/Functional/Console/Commands/ListTasksTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:getJson\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot access property \$author on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot access property \$books on mixed\.$#' + identifier: property.nonObject + count: 2 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot access property \$children on mixed\.$#' + identifier: property.nonObject + count: 3 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot access property \$name on mixed\.$#' + identifier: property.nonObject + count: 2 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method content\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method first\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method getHashedKey\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method has\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method last\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Parameter \#2 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Parameter \#3 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Parameter \#4 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Functional/Core/Models/BaseModelTest.php + + - + message: '#^Call to an undefined method Pest\\Expectation\:\:relationLoaded\(\)\.$#' + identifier: method.notFound + count: 3 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Pest\\Expectation\\:\:relationLoaded\(\)\.$#' + identifier: method.notFound + count: 2 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method each\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method has\(\) on mixed\.$#' + identifier: method.nonObject + count: 4 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method toBeFalse\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method toBeTrue\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Functional/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:patchJson\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Core/Requests/RequestTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Core/Requests/RequestTest.php + + - + message: '#^Cannot call method json\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Functional/Core/Requests/RequestTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$app\.$#' + identifier: property.notFound + count: 1 + path: tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:get\(\)\.$#' + identifier: method.notFound + count: 2 + path: tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php + + - + message: '#^Cannot call method assertOk\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php + + - + message: '#^Cannot call method providerIsLoaded\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php + + - + message: '#^Parameter \#1 \$key of method Illuminate\\Support\\Collection\<\(int\|string\),mixed\>\:\:has\(\) expects array\<\(int\|string\)\>\|int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$app\.$#' + identifier: property.notFound + count: 1 + path: tests/Functional/Foundation/Providers/ApiatoServiceProviderTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:artisan\(\)\.$#' + identifier: method.notFound + count: 3 + path: tests/Functional/Foundation/Providers/ApiatoServiceProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Providers/ApiatoServiceProviderTest.php + + - + message: '#^Cannot call method assertExitCode\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Functional/Foundation/Providers/ApiatoServiceProviderTest.php + + - + message: '#^Cannot call method providerIsLoaded\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Functional/Foundation/Providers/ApiatoServiceProviderTest.php + + - + message: '#^Call to an undefined method Mockery\\ExpectationInterface\|Mockery\\ExpectsHigherOrderMessage\:\:withArgs\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Support/Providers/ConfigServiceProviderTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:artisan\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Support/Providers/ConfigServiceProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Support/Providers/ConfigServiceProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Support/Providers/HelperServiceProviderTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$app\.$#' + identifier: property.notFound + count: 3 + path: tests/Functional/Foundation/Support/Providers/LocalizationServiceProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Support/Providers/LocalizationServiceProviderTest.php + + - + message: '#^Cannot call method setLocale\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Functional/Foundation/Support/Providers/LocalizationServiceProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Support/Providers/MigrationServiceProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Foundation/Support/Providers/ViewServiceProviderTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$repository\.$#' + identifier: property.notFound + count: 5 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$user\.$#' + identifier: property.notFound + count: 5 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot access property \$children on mixed\.$#' + identifier: property.nonObject + count: 3 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot access property \$id on mixed\.$#' + identifier: property.nonObject + count: 4 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot call method findById\(\) on mixed\.$#' + identifier: method.nonObject + count: 4 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot call method first\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot call method has\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot call method relationLoaded\(\) on mixed\.$#' + identifier: method.nonObject + count: 13 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Cannot call method with\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Functional/IncludeEagerLoadingTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Functional/Macros/MacroServiceProviderTest.php + + - + message: '#^Cannot call method freezeTime\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Pest.php + + - + message: '#^Cannot call method toBe\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Pest.php + + - + message: '#^Undefined variable\: \$this$#' + identifier: variable.undefined + count: 2 + path: tests/Pest.php + + - + message: '#^Access to an undefined property Pest\\Mixins\\Expectation\\>\|Pest\\Mixins\\Expectation\\:\:\$email\.$#' + identifier: property.notFound + count: 1 + path: tests/Unit/Core/Actions/ActionTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Actions/ActionTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Actions/ActionTest.php + + - + message: '#^Cannot call method toBe\(\) on Pest\\Expectation\\|Pest\\Expectations\\EachExpectation\\|Pest\\Expectations\\HigherOrderExpectation\, Workbench\\App\\Containers\\Identity\\User\\Models\\User\|null\>\|Pest\\Expectations\\OppositeExpectation\\|Workbench\\App\\Containers\\Identity\\User\\Models\\User\|null\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Actions/ActionTest.php + + - + message: '#^Cannot call method toBe\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Actions/ActionTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$model\.$#' + identifier: property.notFound + count: 2 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot access property \$author_id on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot access property \$id on mixed\.$#' + identifier: property.nonObject + count: 2 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot access property \$title on mixed\.$#' + identifier: property.nonObject + count: 3 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method count\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method create\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 6 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method getHashedKey\(\) on mixed\.$#' + identifier: method.nonObject + count: 5 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method getKey\(\) on mixed\.$#' + identifier: method.nonObject + count: 4 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method is\(\) on Illuminate\\Database\\Eloquent\\Model\|null\.$#' + identifier: method.nonObject + count: 4 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Cannot call method makeOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 6 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Parameter \#1 \$class of function class_uses_recursive expects object\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Parameter \#1 \.\.\.\$numbers of method Apiato\\Support\\HashidsManagerDecorator\:\:encode\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 5 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Property Apiato\\Core\\Models\\BaseModel@anonymous/tests/Unit/Core/Models/BaseModelTest\.php\:12\:\:\$resourceKey has no type specified\.$#' + identifier: missingType.property + count: 1 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Short ternary operator is not allowed\. Use null coalesce operator if applicable or consider using long ternary\.$#' + identifier: ternary.shortNotAllowed + count: 1 + path: tests/Unit/Core/Models/BaseModelTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Repositories/Exceptions/ResourceCreationFailedTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Repositories/Exceptions/ResourceNotFoundTest.php + + - + message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$email\.$#' + identifier: property.notFound + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' + identifier: property.notFound + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$app\.$#' + identifier: property.notFound + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Access to an undefined property Workbench\\App\\Containers\\MySection\\Book\\Models\\Book\:\:\$title\.$#' + identifier: property.notFound + count: 5 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Collection\<\(int\|string\), Workbench\\App\\Containers\\MySection\\Book\\Models\\Book\>\|Workbench\\App\\Containers\\MySection\\Book\\Models\\Book\:\:getKey\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Mockery\\ExpectationInterface\|Mockery\\HigherOrderMessage\|Mockery\\MockInterface\:\:andReturnUsing\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Mockery\\MockInterface\:\:canSkipPagination\(\)\.$#' + identifier: method.notFound + count: 2 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Mockery\\MockInterface\:\:exceedsMaxPaginationLimit\(\)\.$#' + identifier: method.notFound + count: 2 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:assertModelExists\(\)\.$#' + identifier: method.notFound + count: 3 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:assertModelMissing\(\)\.$#' + identifier: method.notFound + count: 3 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Pest\\Expectation\\>\:\:first\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot access property \$email on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot access property \$id on Workbench\\App\\Containers\\MySection\\Book\\Models\\Book\|null\.$#' + identifier: property.nonObject + count: 2 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot access property \$id on mixed\.$#' + identifier: property.nonObject + count: 11 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot access property \$name on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot access property \$title on mixed\.$#' + identifier: property.nonObject + count: 2 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method and\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method create\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 10 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method getCacheKey\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method last\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method make\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method makeOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method offsetGet\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method orWhere\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method pluck\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method toArray\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method toBe\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Cannot call method where\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Method Workbench\\App\\Containers\\Identity\\User\\Data\\Repositories\\UserRepository@anonymous/tests/Unit/Core/Repositories/RepositoryTest\.php\:208\:\:getScopes\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 9 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$data of method Apiato\\Core\\Repositories\\Repository\\:\:store\(\) expects array\|Illuminate\\Contracts\\Support\\Arrayable, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$id of method Apiato\\Core\\Repositories\\Repository\\:\:delete\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$id of method Apiato\\Core\\Repositories\\Repository\\:\:find\(\) expects array\|Illuminate\\Contracts\\Support\\Arrayable\|int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$id of method Apiato\\Core\\Repositories\\Repository\\:\:findById\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$id of method Apiato\\Core\\Repositories\\Repository\\:\:findOrFail\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$ids of method Apiato\\Core\\Repositories\\Repository\\:\:findMany\(\) expects array\|Illuminate\\Contracts\\Support\\Arrayable, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$key of static method Illuminate\\Support\\Facades\\Cache\:\:has\(\) expects array\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$key of static method Illuminate\\Support\\Facades\\Cache\:\:missing\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#1 \$model of method Apiato\\Core\\Repositories\\Repository\\:\:save\(\) expects Workbench\\App\\Containers\\MySection\\Book\\Models\\Book, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#2 \$id of method Apiato\\Core\\Repositories\\Repository\\:\:update\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Parameter \#2 \$values of method Apiato\\Core\\Repositories\\Repository\\:\:firstOrCreate\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Unable to resolve the template type TAndValue in call to method Pest\\Expectation\\:\:and\(\)$#' + identifier: argument.templateType + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Unable to resolve the template type TAndValue in call to method Pest\\Expectation\\:\:and\(\)$#' + identifier: argument.templateType + count: 4 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Unable to resolve the template type TAndValue in call to method Pest\\Expectation\\:\:and\(\)$#' + identifier: argument.templateType + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Unable to resolve the template type TValue in call to function expect$#' + identifier: argument.templateType + count: 5 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Variable method call on mixed\.$#' + identifier: method.dynamicName + count: 1 + path: tests/Unit/Core/Repositories/RepositoryTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Requests/RequestTest.php + + - + message: '#^Dynamic call to static method Apiato\\Core\\Requests\\Request@anonymous/tests/Unit/Core/Requests/RequestTest\.php\:9\:\:sanitize\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: tests/Unit/Core/Requests/RequestTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$sut\.$#' + identifier: property.notFound + count: 2 + path: tests/Unit/Core/Tests/TestCaseTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Tests/TestCaseTest.php + + - + message: '#^Parameter \#1 \$class of function class_uses_recursive expects object\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Core/Tests/TestCaseTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Core/Transformers/TransformerTest.php + + - + message: '#^Cannot call method make\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Core/Transformers/TransformerTest.php + + - + message: '#^Cannot call method makeOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Core/Transformers/TransformerTest.php + + - + message: '#^Out of 383 possible param types, only 340 \- 88\.7 %% actually have it\. Add more param types to get over 99 %%$#' + identifier: typeCoverage.paramTypeCoverage + count: 2 + path: tests/Unit/Core/Transformers/TransformerTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/ApiatoTest.php + + - + message: '#^Parameter \#1 \$callback of method Apiato\\Foundation\\Configuration\\Factory\\:\:resolveFactoryNameUsing\(\) expects Closure\(class\-string\\)\: \(class\-string\\|null\), Closure\(string\)\: ''test'' given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Foundation/ApiatoTest.php + + - + message: '#^Parameter \#1 \$callback of method Apiato\\Foundation\\Configuration\\Repository\\:\:resolveModelNameUsing\(\) expects Closure\(class\-string\\)\: class\-string\, Closure\(string\)\: ''test'' given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Foundation/ApiatoTest.php + + - + message: '#^Parameter \#1 \$callback of method Apiato\\Foundation\\Configuration\\Seeding\\:\:sortUsing\(\) expects Closure\(array\, non\-empty\-string\>\>\)\: class\-string\, Closure\(array\)\: array\{''test''\} given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Foundation/ApiatoTest.php + + - + message: '#^Parameter \#1 \$modelName of method Apiato\\Foundation\\Configuration\\Factory\\:\:resolveFactoryName\(\) expects class\-string\, string given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Foundation/ApiatoTest.php + + - + message: '#^Parameter \#1 \$repositoryName of method Apiato\\Foundation\\Configuration\\Repository\\:\:resolveModelName\(\) expects class\-string\, string given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Foundation/ApiatoTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Configuration/FactoryTest.php + + - + message: '#^Parameter \#1 \$modelName of method Apiato\\Foundation\\Configuration\\Factory\\:\:resolveFactoryName\(\) expects class\-string\, string given\.$#' + identifier: argument.type + count: 2 + path: tests/Unit/Foundation/Configuration/FactoryTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Configuration/LocalizationTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Configuration/ProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Configuration/RepositoryTest.php + + - + message: '#^Parameter \#1 \$repositoryName of method Apiato\\Foundation\\Configuration\\Repository\\:\:resolveModelName\(\) expects class\-string\, string given\.$#' + identifier: argument.type + count: 3 + path: tests/Unit/Foundation/Configuration/RepositoryTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Configuration/RoutingTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Configuration/SeedingTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Configuration/ViewTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:assertDatabaseCount\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Database/DatabaseSeederTest.php + + - + message: '#^Call to an undefined method PHPUnit\\Framework\\TestCase\:\:assertDatabaseHas\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Database/DatabaseSeederTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Foundation/Database/DatabaseSeederTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Foundation/Database/DatabaseSeederTest.php + + - + message: '#^Parameter \#1 \$callback of method Apiato\\Foundation\\Configuration\\Seeding\\:\:sortUsing\(\) expects Closure\(array\, non\-empty\-string\>\>\)\: class\-string\, Closure\(\)\: array\{''AnonymousClass9d10017f3e9f351a03aca329a51dd202''\} given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Foundation/Database/DatabaseSeederTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$next\.$#' + identifier: property.notFound + count: 8 + path: tests/Unit/Http/Middleware/ProcessETagTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Http/Middleware/ProcessETagTest.php + + - + message: '#^Call to protected method expectException\(\) of class PHPUnit\\Framework\\TestCase\.$#' + identifier: method.protected + count: 1 + path: tests/Unit/Http/Middleware/ProcessETagTest.php + + - + message: '#^Parameter \#2 \$next of method Apiato\\Http\\Middleware\\ProcessETag\:\:handle\(\) expects Closure\(Illuminate\\Http\\Request\)\: Symfony\\Component\\HttpFoundation\\Response, mixed given\.$#' + identifier: argument.type + count: 7 + path: tests/Unit/Http/Middleware/ProcessETagTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$next\.$#' + identifier: property.notFound + count: 3 + path: tests/Unit/Http/Middleware/ValidateJsonContentTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Http/Middleware/ValidateJsonContentTest.php + + - + message: '#^Call to protected method expectException\(\) of class PHPUnit\\Framework\\TestCase\.$#' + identifier: method.protected + count: 1 + path: tests/Unit/Http/Middleware/ValidateJsonContentTest.php + + - + message: '#^Parameter \#1 \.\.\.\$data of method Pest\\PendingCalls\\TestCall\:\:with\(\) expects array\\|string\>\|Closure\|string, array\{true, false\} given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Http/Middleware/ValidateJsonContentTest.php + + - + message: '#^Parameter \#2 \$next of method Apiato\\Http\\Middleware\\ValidateJsonContent\:\:handle\(\) expects Closure\(Illuminate\\Http\\Request\)\: Symfony\\Component\\HttpFoundation\\Response, mixed given\.$#' + identifier: argument.type + count: 2 + path: tests/Unit/Http/Middleware/ValidateJsonContentTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Http/RequestRelationTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Http/Resources/CollectionTest.php + + - + message: '#^Cannot call method getIterator\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Http/Resources/CollectionTest.php + + - + message: '#^Cannot call method make\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Http/Resources/CollectionTest.php + + - + message: '#^Cannot call method makeOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Http/Resources/CollectionTest.php + + - + message: '#^Function json_encode is unsafe to use\. It can return FALSE instead of throwing an exception\. Please add ''use function Safe\\json_encode;'' at the beginning of the file to use the variant provided by the ''thecodingmachine/safe'' library\.$#' + identifier: theCodingMachineSafe.function + count: 1 + path: tests/Unit/Http/Resources/CollectionTest.php + + - + message: '#^Parameter \#2 \$message of method Pest\\Mixins\\Expectation\\:\:toBe\(\) expects string, string\|false given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Http/Resources/CollectionTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Http/Resources/ItemTest.php + + - + message: '#^Cannot call method getIterator\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Http/Resources/ItemTest.php + + - + message: '#^Cannot call method make\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Http/Resources/ItemTest.php + + - + message: '#^Cannot call method makeOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Http/Resources/ItemTest.php + + - + message: '#^Function json_encode is unsafe to use\. It can return FALSE instead of throwing an exception\. Please add ''use function Safe\\json_encode;'' at the beginning of the file to use the variant provided by the ''thecodingmachine/safe'' library\.$#' + identifier: theCodingMachineSafe.function + count: 1 + path: tests/Unit/Http/Resources/ItemTest.php + + - + message: '#^Parameter \#2 \$message of method Pest\\Mixins\\Expectation\\:\:toBe\(\) expects string, string\|false given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Http/Resources/ItemTest.php + + - + message: '#^Access to an undefined property PHPUnit\\Framework\\TestCase\:\:\$app\.$#' + identifier: property.notFound + count: 2 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Call to an undefined method Pest\\Expectation\\:\:getStatusCode\(\)\.$#' + identifier: method.notFound + count: 3 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method create\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method createOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method for\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method getCurrentScope\(\) on \(callable\)\|League\\Fractal\\TransformerAbstract\|string\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method getData\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method has\(\) on mixed\.$#' + identifier: method.nonObject + count: 4 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method makeOne\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method toBe\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Cannot call method toHaveKey\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Function getUser\(\) should return Workbench\\App\\Containers\\Identity\\User\\Models\\User but returns mixed\.$#' + identifier: return.type + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Parameter \#1 \$include of method League\\Fractal\\Manager\:\:getIncludeParams\(\) expects string, string\|null given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Parameter \#1 \$key of method Illuminate\\Testing\\Fluent\\AssertableJson\:\:has\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 3 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Parameter \#1 \$key of method Illuminate\\Testing\\Fluent\\AssertableJson\:\:missing\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 3 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Parameter \#1 \$resourceName of method Spatie\\Fractalistic\\Fractal\:\:withResourceName\(\) expects string, bool\|null given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Http/ResponseTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Macros/MacroServiceProviderTest.php + + - + message: '#^Dynamic call to static method Illuminate\\Support\\Collection\\>\:\:containsDecodedHash\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: tests/Unit/Macros/MacroServiceProviderTest.php + + - + message: '#^Dynamic call to static method Illuminate\\Support\\Collection\\:\:decode\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 2 + path: tests/Unit/Macros/MacroServiceProviderTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Support/DefaultProvidersTest.php + + - + message: '#^Anonymous function should return Pest\\Expectation but returns Pest\\Mixins\\Expectation\\.$#' + identifier: return.type + count: 10 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Anonymous function should return Pest\\Expectation but returns Pest\\Mixins\\Expectation\\.$#' + identifier: return.type + count: 8 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Anonymous function should return Pest\\Expectation but returns Pest\\Mixins\\Expectation\\.$#' + identifier: return.type + count: 5 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Parameter \#3 \$headers of static method Apiato\\Http\\Response\:\:json\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Static method Apiato\\Support\\Facades\\Response\:\:accepted\(\) invoked with 3 parameters, 0 required\.$#' + identifier: arguments.count + count: 1 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Static method Apiato\\Support\\Facades\\Response\:\:created\(\) invoked with 3 parameters, 0 required\.$#' + identifier: arguments.count + count: 1 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Static method Apiato\\Support\\Facades\\Response\:\:noContent\(\) invoked with 2 parameters, 0 required\.$#' + identifier: arguments.count + count: 1 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Static method Apiato\\Support\\Facades\\Response\:\:ok\(\) invoked with 3 parameters, 0 required\.$#' + identifier: arguments.count + count: 1 + path: tests/Unit/Support/Facades/ResponseTest.php + + - + message: '#^Anonymous function should return Pest\\Expectation but returns Pest\\Mixins\\Expectation\<\(Closure\)\|null\>\.$#' + identifier: return.type + count: 4 + path: tests/Unit/Support/HashidsManagerDecoratorTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Support/HashidsManagerDecoratorTest.php + + - + message: '#^Dynamic call to static method GrahamCampbell\\Manager\\AbstractManager\:\:connection\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: tests/Unit/Support/HashidsManagerDecoratorTest.php + + - + message: '#^Dynamic call to static method GrahamCampbell\\Manager\\AbstractManager\:\:getDefaultConnection\(\)\.$#' + identifier: staticMethod.dynamicCall + count: 1 + path: tests/Unit/Support/HashidsManagerDecoratorTest.php + + - + message: '#^Function class_parents is unsafe to use\. It can return FALSE instead of throwing an exception\. Please add ''use function Safe\\class_parents;'' at the beginning of the file to use the variant provided by the ''thecodingmachine/safe'' library\.$#' + identifier: theCodingMachineSafe.function + count: 1 + path: tests/Unit/Support/HashidsManagerDecoratorTest.php + + - + message: '#^Function class_uses is unsafe to use\. It can return FALSE instead of throwing an exception\. Please add ''use function Safe\\class_uses;'' at the beginning of the file to use the variant provided by the ''thecodingmachine/safe'' library\.$#' + identifier: theCodingMachineSafe.function + count: 2 + path: tests/Unit/Support/HashidsManagerDecoratorTest.php + + - + message: '#^Parameter \#1 \.\.\.\$numbers of method Apiato\\Support\\HashidsManagerDecorator\:\:encode\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Support/HashidsManagerDecoratorTest.php + + - + message: '#^Call to an undefined method Pest\\PendingCalls\\DescribeCall\:\:covers\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/Unit/Support/SanitizerTest.php + + - + message: '#^Parameter \#1 \$source of static method Apiato\\Support\\Sanitizer\:\:sanitize\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Support/SanitizerTest.php + + - + message: '#^Parameter \#2 \$fields of static method Apiato\\Support\\Sanitizer\:\:sanitize\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: tests/Unit/Support/SanitizerTest.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 000000000..c54066911 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,23 @@ +#https://phpstan.org/user-guide/getting-started + +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - config + - src + - tests + - .php-cs-fixer.dist.php + tmpDir: phpstan_cache + excludePaths: + - tests/Support/Doubles/Fakes/Laravel/vendor (?) + + reportUnmatchedIgnoredErrors: true + +# ignoreErrors: +# - '#PHPDoc tag @var#' + + fileExtensions: + - php diff --git a/phpstan.neon.dist b/phpstan.neon.dist deleted file mode 100644 index 2e6d80242..000000000 --- a/phpstan.neon.dist +++ /dev/null @@ -1,16 +0,0 @@ -includes: - - vendor/phpstan/phpstan-strict-rules/rules.neon - - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon - - vendor/larastan/larastan/extension.neon - - vendor/nesbot/carbon/extension.neon - - vendor/phpstan/phpstan-phpunit/extension.neon -parameters: - level: max - paths: - - config - - src - - tests - - .php-cs-fixer.dist.php - tmpDir: phpstan_cache - excludePaths: - - tests/Support/Doubles/Fakes/Laravel/vendor (?) diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 0a3ccfeaa..000000000 --- a/psalm.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/rector.php b/rector.php index 0d292ceb6..a62dc66a9 100644 --- a/rector.php +++ b/rector.php @@ -2,16 +2,133 @@ declare(strict_types=1); +use Apiato\Linters\Rector\AssertInstanceToStaticCallRector; +use Apiato\Linters\Rector\MockObjectStaticToInstanceCallRector; +use Rector\Caching\ValueObject\Storage\FileCacheStorage; +use Rector\CodeQuality\Rector\ClassMethod\LocallyCalledStaticMethodToNonStaticRector; +use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector; +use Rector\CodeQuality\Rector\Identical\SimplifyBoolIdenticalTrueRector; +use Rector\CodeQuality\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector; +use Rector\CodingStyle\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector; +use Rector\CodingStyle\Rector\PostInc\PostIncDecToPreIncDecRector; +use Rector\CodingStyle\Rector\Use_\SeparateMultiUseImportsRector; use Rector\Config\RectorConfig; +use Rector\DeadCode\Rector\StaticCall\RemoveParentCallWithoutParentRector; +use Rector\Doctrine\Dbal211\Rector\MethodCall\ReplaceFetchAllMethodCallRector; +use Rector\Doctrine\Orm214\Rector\Param\ReplaceLifecycleEventArgsByDedicatedEventArgsRector; +use Rector\Naming\Rector\Assign\RenameVariableToMatchMethodCallReturnTypeRector; +use Rector\Naming\Rector\Class_\RenamePropertyToMatchTypeRector; +use Rector\Naming\Rector\ClassMethod\RenameParamToMatchTypeRector; +use Rector\Naming\Rector\ClassMethod\RenameVariableToMatchNewTypeRector; +use Rector\Naming\Rector\Foreach_\RenameForeachValueVariableToMatchExprVariableRector; +use Rector\Naming\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector; +use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector; +use Rector\Php73\Rector\BooleanOr\IsCountableRector; +use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector; +use Rector\Php74\Rector\Property\RestoreDefaultNullToNullableTypePropertyRector; +use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; +use Rector\PHPUnit\Set\PHPUnitSetList; +use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector; +use Rector\Set\ValueObject\LevelSetList; +use Rector\Set\ValueObject\SetList; +use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromCreateMockAssignRector; +use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector; +use Rector\ValueObject\PhpVersion; +use RectorLaravel\Set\LaravelLevelSetList; +use RectorLaravel\Set\LaravelSetList; return RectorConfig::configure() + ->withCache( + // specify a path that works locally as well as on CI job runners + cacheDirectory: '/tmp/rector', + // ensure file system caching is used instead of in-memory + cacheClass: FileCacheStorage::class + ) + ->withRootFiles() + // https://getrector.com/documentation/troubleshooting-parallel + ->withParallel(360, 2, 40) + ->withImportNames(importDocBlockNames: false, importShortClasses: false) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + typeDeclarations: true, + privatization: true, + naming: true, + instanceOf: true, + earlyReturn: true, + strictBooleans: true, + carbon: true, + rectorPreset: true, + phpunitCodeQuality: true, + doctrineCodeQuality: false, + symfonyCodeQuality: true, + symfonyConfigs: false, + ) + ->withComposerBased( + phpunit: true, + ) + ->withAttributesSets( + phpunit: true, + ) + ->withRules([ + ReplaceFetchAllMethodCallRector::class, + ReplaceLifecycleEventArgsByDedicatedEventArgsRector::class, + MockObjectStaticToInstanceCallRector::class, + AssertInstanceToStaticCallRector::class, + ]) + ->withSets([ + PHPUnitSetList::PHPUNIT_90, + PHPUnitSetList::PHPUNIT_100, + PHPUnitSetList::PHPUNIT_110, + PHPUnitSetList::PHPUNIT_CODE_QUALITY, + PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES, + + SetList::CODE_QUALITY, + SetList::CODING_STYLE, + SetList::TYPE_DECLARATION, + SetList::EARLY_RETURN, + SetList::STRICT_BOOLEANS, + + LevelSetList::UP_TO_PHP_82, + LaravelLevelSetList::UP_TO_LARAVEL_120, + LaravelSetList::LARAVEL_120, + LaravelSetList::LARAVEL_CODE_QUALITY, + LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL, + ]) + ->withPhpVersion(PhpVersion::PHP_82) ->withPaths([ __DIR__ . '/config', __DIR__ . '/src', __DIR__ . '/tests', __DIR__ . '/workbench', ]) - ->withPhpSets(php82: true) - ->withTypeCoverageLevel(100) - ->withDeadCodeLevel(100) - ->withCodeQualityLevel(100); + ->withSkip([ + __DIR__ . '/workbench/bootstrap/', + SimplifyBoolIdenticalTrueRector::class, // it's breaks the Routers + IsCountableRector::class, // this rule does not fit, a lot of where it goes wrong + RestoreDefaultNullToNullableTypePropertyRector::class, // don't work with DTO nullable parameter + RemoveExtraParametersRector::class, // catting an argument in dump() function + ClosureToArrowFunctionRector::class, // it's breaks the Routers + SeparateMultiUseImportsRector::class, // it's breaks the using multiple Traits + LocallyCalledStaticMethodToNonStaticRector::class, + PreferPHPUnitThisCallRector::class, // it's breaks with phpstan + RenamePropertyToMatchTypeRector::class, // it's breaks the Entity + RenameVariableToMatchMethodCallReturnTypeRector::class, // it's redundant + RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class, // it's redundant + RenameForeachValueVariableToMatchExprVariableRector::class, // it's breaks the unit tests + TypedPropertyFromCreateMockAssignRector::class, // it's breaks the unit tests + RenameVariableToMatchNewTypeRector::class, // it's breaks the unit tests + + // THINKING + AddMethodCallBasedStrictParamTypeRector::class, // it's breaks the using multiple Traits + FlipTypeControlToUseExclusiveTypeRector::class, + IssetOnPropertyObjectToPropertyExistsRector::class, + RenameParamToMatchTypeRector::class, + PostIncDecToPreIncDecRector::class, + + // WAITING FIX + MakeInheritedMethodVisibilitySameAsParentRector::class, + RemoveParentCallWithoutParentRector::class, + ]) + ->withFileExtensions(['php']); diff --git a/ruleset.xml b/ruleset.xml deleted file mode 100644 index 8b9d18b08..000000000 --- a/ruleset.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - Apiato PHPMD rulesets - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - diff --git a/src/Console/CommandServiceProvider.php b/src/Console/CommandServiceProvider.php index 63b2f3576..f0f75c207 100644 --- a/src/Console/CommandServiceProvider.php +++ b/src/Console/CommandServiceProvider.php @@ -1,5 +1,7 @@ beforeLast(DIRECTORY_SEPARATOR) ->afterLast(DIRECTORY_SEPARATOR) ->value())->each(function (Collection $files, string $group): void { - $this->comment("[{$group}]"); + $this->comment(\sprintf('[%s]', $group)); /** @var SplFileInfo $file */ foreach ($files as $file) { @@ -38,10 +43,10 @@ public function handle(): void ->headline(); if ($this->option('with-file-name')) { - $includeFileName = "({$originalFileName})"; - $this->info(" - {$fileName} {$includeFileName}"); + $includeFileName = \sprintf('(%s)', $originalFileName); + $this->info(\sprintf(' - %s %s', $fileName, $includeFileName)); } else { - $this->info(" - {$fileName}"); + $this->info(' - ' . $fileName); } } }); diff --git a/src/Console/Commands/ListTasks.php b/src/Console/Commands/ListTasks.php index 6347bcf60..303798a58 100644 --- a/src/Console/Commands/ListTasks.php +++ b/src/Console/Commands/ListTasks.php @@ -1,5 +1,7 @@ beforeLast(DIRECTORY_SEPARATOR) ->afterLast(DIRECTORY_SEPARATOR) ->value())->each(function (Collection $files, string $group): void { - $this->comment("[{$group}]"); + $this->comment(\sprintf('[%s]', $group)); /** @var SplFileInfo $file */ foreach ($files as $file) { @@ -38,10 +43,10 @@ public function handle(): void ->headline(); if ($this->option('with-file-name')) { - $includeFileName = "({$originalFileName})"; - $this->info(" - {$fileName} {$includeFileName}"); + $includeFileName = \sprintf('(%s)', $originalFileName); + $this->info(\sprintf(' - %s %s', $fileName, $includeFileName)); } else { - $this->info(" - {$fileName}"); + $this->info(' - ' . $fileName); } } }); diff --git a/src/Core/Actions/Action.php b/src/Core/Actions/Action.php index f54a9df59..83c2eb397 100644 --- a/src/Core/Actions/Action.php +++ b/src/Core/Actions/Action.php @@ -1,5 +1,7 @@ static::run(...$args)); + return DB::transaction(fn (): mixed => $this->run(...$args)); } } diff --git a/src/Core/Actions/SubAction.php b/src/Core/Actions/SubAction.php index 089010c14..baaef371b 100644 --- a/src/Core/Actions/SubAction.php +++ b/src/Core/Actions/SubAction.php @@ -1,5 +1,7 @@ factory() ->resolveFactoryName(static::class); - if (is_string($factoryName)) { + if (\is_string($factoryName)) { return $factoryName::new(); } diff --git a/src/Core/Models/Concerns/HandlesHashedIdRouteModelBinding.php b/src/Core/Models/Concerns/HandlesHashedIdRouteModelBinding.php index b517e1128..65a44ffd6 100644 --- a/src/Core/Models/Concerns/HandlesHashedIdRouteModelBinding.php +++ b/src/Core/Models/Concerns/HandlesHashedIdRouteModelBinding.php @@ -1,5 +1,7 @@ getKeyName(); } $attribute = $this->getAttribute($field); - if (is_null($attribute)) { + if (\is_null($attribute)) { return null; } diff --git a/src/Core/Models/InteractsWithApiato.php b/src/Core/Models/InteractsWithApiato.php index c11453175..71ee3bec9 100644 --- a/src/Core/Models/InteractsWithApiato.php +++ b/src/Core/Models/InteractsWithApiato.php @@ -1,5 +1,7 @@ scope(function (Builder|Model $model) use ($requestRelation): Builder|Model { + $this->scope(static function (Builder|Model $model) use ($requestRelation): Builder|Model { if ($requestRelation->requestingIncludes()) { if ($model instanceof Model) { return $model->with($requestRelation->getValidRelationsFor($model)); @@ -106,12 +110,13 @@ public function getModel() /** * Retrieve all data of repository, paginated. * - * @param int|null $limit The number of entries per page. If set to 0, the pagination is disabled. - * @param array $columns - * @param string $method + * @param int|null $limit The number of entries per page. If set to 0, the pagination is disabled. + * @param array $columns + * @param string $method * * @throws RepositoryException */ + #[\Override] public function paginate($limit = null, $columns = ['*'], $method = 'paginate'): mixed { $limit = $this->setPaginationLimit($limit); @@ -128,7 +133,7 @@ public function paginate($limit = null, $columns = ['*'], $method = 'paginate'): return parent::paginate($limit, $columns, $method); } - $key = $this->getCacheKey('paginate', func_get_args()); + $key = $this->getCacheKey('paginate', \func_get_args()); $time = $this->getCacheTime(); $value = $this->getCacheRepository()->remember($key, $time, function () use ($limit, $columns, $method) { @@ -141,26 +146,26 @@ public function paginate($limit = null, $columns = ['*'], $method = 'paginate'): return $value; } - public function setPaginationLimit($limit): mixed + public function setPaginationLimit(null|int|string $limit = null): int { - // the priority is for the function parameter, if not available then take - // it from the request if available and if not keep it null. - return $limit ?? request()?->input('limit'); + // The priority is for the function parameter, if not available then take it + // from the request if available and if not keep it null. + return (int)($limit ?? request()?->input('limit', 0)); } - public function wantsToSkipPagination(string|int|null $limit): bool + public function wantsToSkipPagination(int $limit): bool { - return '0' === $limit || 0 === $limit; + return $limit === 0; } public function canSkipPagination(): mixed { - // check local (per repository) rule - if (!is_null($this->allowDisablePagination)) { + // Check local (per repository) rule + if (!\is_null($this->allowDisablePagination)) { return $this->allowDisablePagination; } - // check global (.env) rule + // Check global (.env) rule return config('repository.pagination.skip'); } @@ -179,7 +184,7 @@ public function all($columns = ['*']) return parent::all($columns); } - $key = $this->getCacheKey('all', func_get_args()); + $key = $this->getCacheKey('all', \func_get_args()); $time = $this->getCacheTime(); $value = $this->getCacheRepository()->remember($key, $time, function () use ($columns) { return parent::all($columns); @@ -194,6 +199,7 @@ public function all($columns = ['*']) public function resetScope(): static { parent::resetScope(); + $this->resetScopes(); return $this; @@ -214,125 +220,41 @@ public function exceedsMaxPaginationLimit(mixed $limit): bool /** * @throws RepositoryException */ - public function addRequestCriteria(array $fieldsToDecode = ['id']): static + public function addRequestCriteria(): static { - $this->pushCriteria(app(RequestCriteria::class)); if ($this->shouldDecodeSearch()) { - $this->decodeSearchQueryString($fieldsToDecode); - } - - return $this; - } - - private function shouldDecodeSearch(): bool - { - return config('apiato.hash-id') && $this->isSearching(request()->query()); - } - - private function isSearching(array $query): bool - { - return array_key_exists('search', $query) && $query['search']; - } - - public function decodeSearchQueryString(array $fieldsToDecode): void - { - $query = request()->query(); - $searchQuery = $query['search']; - - $decodedValue = $this->decodeValue($searchQuery); - $decodedData = $this->decodeData($fieldsToDecode, $searchQuery); - - $decodedQuery = $this->arrayToSearchQuery($decodedData); - - if ($decodedValue) { - if (empty($decodedQuery)) { - $decodedQuery .= $decodedValue; - } else { - $decodedQuery .= (';' . $decodedValue); - } + $this->decodeSearchQueryString(); } - $query['search'] = $decodedQuery; - - request()->query->replace($query); - } - - private function decodeValue(string $searchQuery): string|int|null - { - $searchValue = $this->parserSearchValue($searchQuery); - - if (is_string($searchValue)) { - return hashids()->decode($searchValue) ?? $searchValue; - } - - return null; - } - - private function parserSearchValue($search) - { - if (strpos((string) $search, ';') || strpos((string) $search, ':')) { - $values = explode(';', (string) $search); - foreach ($values as $value) { - $s = explode(':', $value); - if (1 === count($s)) { - return $s[0]; - } - } - - return null; - } + $this->pushCriteria(app(RequestCriteria::class)); - return $search; + return $this; } - private function decodeData(array $fieldsToDecode, string $searchQuery): array + public function decodeSearchQueryString(): void { - $searchArray = $this->parserSearchData($searchQuery); + /** @var Request $request */ + $request = app(Request::class); + $query = $request->query(); + $searchKey = config('repository.criteria.params.search', 'search'); + $searchQuery = $query[$searchKey]; - foreach ($fieldsToDecode as $field) { - if (array_key_exists($field, $searchArray)) { - $searchArray[$field] = hashids()->decodeOrFail($searchArray[$field]); - } + if (\is_string($searchQuery) === false || $searchQuery === '') { + return; } - return $searchArray; - } + $searchData = $this->parserSearchData($searchQuery); + $decodedData = $this->getDecodedSearchValues($searchData); - private function parserSearchData($search): array - { - $searchData = []; - - if (strpos((string) $search, ':')) { - $fields = explode(';', (string) $search); - - foreach ($fields as $row) { - try { - [$field, $value] = explode(':', $row); - $searchData[$field] = $value; - } catch (\Exception) { - // Surround offset error - } - } + if ($decodedData === $searchData) { + return; } - return $searchData; - } - - private function arrayToSearchQuery(array $decodedSearchArray): string - { - $decodedSearchQuery = ''; + $newSearchQuery = $this->arrayToSearchQuery($decodedData); - $fields = array_keys($decodedSearchArray); - $length = count($fields); - for ($i = 0; $i < $length; ++$i) { - $field = $fields[$i]; - $decodedSearchQuery .= "{$field}:$decodedSearchArray[$field]"; - if (1 !== $length && $i < $length - 1) { - $decodedSearchQuery .= ';'; - } - } + $query[$searchKey] = $newSearchQuery; - return $decodedSearchQuery; + $request->query->replace($query); } public function removeRequestCriteria(): static @@ -361,7 +283,7 @@ public function make(array $attributes) */ public function store(Arrayable|array $data) { - if (is_array($data)) { + if (\is_array($data)) { return $this->create($data); } @@ -379,7 +301,7 @@ public function create(array $attributes) { try { return parent::create($attributes); - } catch (\Exception) { + } catch (\Throwable) { throw ResourceCreationFailed::create(class_basename($this->model())); } } @@ -412,6 +334,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) { /** @var TModel $model */ $model = parent::firstOrCreate($attributes); + if ($model->wasRecentlyCreated) { $model->update($values); } @@ -454,7 +377,7 @@ public function findOrFail(int|string $id, array $columns = ['*']) * Find an entity/s by its primary key. * * @param int|string|array|Arrayable $id - * @param array $columns + * @param array $columns * * @return ($id is array|Arrayable ? Collection : TModel|null) * @@ -467,7 +390,7 @@ public function find($id, $columns = ['*']) return parent::find($id, $columns); } - $key = $this->getCacheKey('find', func_get_args()); + $key = $this->getCacheKey('find', \func_get_args()); $time = $this->getCacheTime(); $value = $this->getCacheRepository()->remember($key, $time, function () use ($id, $columns) { return parent::find($id, $columns); @@ -506,7 +429,7 @@ public function findMany(array|Arrayable $ids, array $columns = ['*']) * Find data by field and value. * * @param (\Closure(static): mixed)|string|array|Expression $field - * @param array $columns + * @param array $columns * * @return Collection */ @@ -516,7 +439,7 @@ public function findByField($field, $value = null, $columns = ['*']) return parent::findByField($field, $value, $columns); } - $key = $this->getCacheKey('findByField', func_get_args()); + $key = $this->getCacheKey('findByField', \func_get_args()); $time = $this->getCacheTime(); $value = $this->getCacheRepository()->remember($key, $time, function () use ($field, $value, $columns) { return parent::findByField($field, $value, $columns); @@ -541,7 +464,7 @@ public function findWhere(array $where, $columns = ['*']) return parent::findWhere($where, $columns); } - $key = $this->getCacheKey('findWhere', func_get_args()); + $key = $this->getCacheKey('findWhere', \func_get_args()); $time = $this->getCacheTime(); $value = $this->getCacheRepository()->remember($key, $time, function () use ($where, $columns) { return parent::findWhere($where, $columns); @@ -571,7 +494,7 @@ public function delete($id): bool /** * @param class-string $criteria Criteria class name - * @param array $args Arguments to pass to the criteria constructor + * @param array $args Arguments to pass to the criteria constructor * * @throws RepositoryException * @throws BindingResolutionException @@ -587,6 +510,7 @@ public function pushCriteriaWith(string $criteria, array $args): static protected function applyScope(): static { parent::applyScope(); + $this->applyScopes(); return $this; @@ -595,12 +519,109 @@ protected function applyScope(): static protected function applyScopes(): static { foreach ($this->scopes as $scope) { - if (!is_callable($scope)) { + if (!\is_callable($scope)) { throw new \RuntimeException('Query scope is not callable'); } + $this->model = $scope($this->model); } return $this; } + + private function parserSearchData(string $search): array + { + $searchData = []; + + if (str_contains($search, ':') === false) { + return $searchData; + } + + $fields = explode(';', $search); + + foreach ($fields as $field) { + if (str_contains($field, ':') === false) { + continue; + } + + $parts = explode(':', $field, 2); + + if (\count($parts) !== 2) { + continue; + } + + $field = trim($parts[0]); + + if ($field === '') { + continue; + } + + $searchData[$field] = trim($parts[1]); + } + + return $searchData; + } + + private function getDecodedSearchValues(array $searchData): array + { + foreach ($searchData as $field => $value) { + $isBool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + + if ($isBool !== null) { + $searchData[$field] = $value; + continue; + } + + if (is_numeric($value)) { + $searchData[$field] = $value; + continue; + } + + $decodedId = hashids()->decode($value); + + if ($decodedId === null) { + $searchData[$field] = $value; + continue; + } + + $searchData[$field] = $decodedId; + } + + return $searchData; + } + + private function shouldDecodeSearch(): bool + { + return config('apiato.hash-id') && $this->isSearching(); + } + + private function isSearching(): bool + { + $searchKey = config('repository.criteria.params.search', 'search'); + /** @var Request $request */ + $request = app(Request::class); + + return $request->filled($searchKey); + } + + /** + * Reconstructs the search string from an array of field => value pairs. + */ + private function arrayToSearchQuery(array $decodedSearchArray): string + { + $decodedSearchQuery = ''; + + $fields = array_keys($decodedSearchArray); + $length = \count($fields); + foreach ($fields as $i => $iValue) { + $field = $iValue; + $decodedSearchQuery .= \sprintf('%s:%s', $field, $decodedSearchArray[$field]); + + if ($length !== 1 && $i < $length - 1) { + $decodedSearchQuery .= ';'; + } + } + + return $decodedSearchQuery; + } } diff --git a/src/Core/Requests/Request.php b/src/Core/Requests/Request.php index 743e7d018..58ac86025 100644 --- a/src/Core/Requests/Request.php +++ b/src/Core/Requests/Request.php @@ -1,5 +1,7 @@ decode; } + /** @inheritDoc */ + #[\Override] public function route($param = null, $default = null) { - if (in_array($param, $this->decode, true) && config('apiato.hash-id')) { + if (\in_array($param, $this->decode, true) && config('apiato.hash-id')) { $value = parent::route($param); - if (is_null($value)) { + if (\is_null($value)) { return $default; } @@ -42,6 +46,8 @@ public function route($param = null, $default = null) return parent::route($param, $default); } + /** @inheritDoc */ + #[\Override] public function input($key = null, $default = null) { if (!config('apiato.hash-id')) { diff --git a/src/Core/Seeders/Seeder.php b/src/Core/Seeders/Seeder.php index c4f722f42..7fcb65aac 100644 --- a/src/Core/Seeders/Seeder.php +++ b/src/Core/Seeders/Seeder.php @@ -1,9 +1,17 @@ $expectedColumns The key is the column name and the value is the column type. * - * Example: $this->assertDatabaseTable('users', ['id' => 'bigint']); + * Example: self::assertDatabaseTable('users', ['id' => 'bigint']); */ - protected function assertDatabaseTable(string $table, array $expectedColumns): void + protected static function assertDatabaseTable(string $table, array $expectedColumns): void { - $this->assertSameSize($expectedColumns, Schema::getColumnListing($table), "Column count mismatch for '{$table}' table."); + self::assertSameSize($expectedColumns, Schema::getColumnListing($table), \sprintf("Column count mismatch for '%s' table.", $table)); foreach ($expectedColumns as $column => $type) { - $this->assertTrue(Schema::hasColumn($table, $column), "Column '{$column}' not found in '{$table}' table."); - $this->assertEquals($type, Schema::getColumnType($table, $column), "Column '{$column}' in '{$table}' table does not match expected {$type} type."); + self::assertTrue(Schema::hasColumn($table, $column), \sprintf("Column '%s' not found in '%s' table.", $column, $table)); + self::assertEquals($type, Schema::getColumnType($table, $column), \sprintf("Column '%s' in '%s' table does not match expected %s type.", $column, $table, $type)); } } } diff --git a/src/Core/Testing/TestCase.php b/src/Core/Testing/TestCase.php index 2638ea203..39797533c 100644 --- a/src/Core/Testing/TestCase.php +++ b/src/Core/Testing/TestCase.php @@ -1,5 +1,7 @@ []; } - public function nullableItem(mixed $data, callable|self $transformer, string|null $resourceKey = null): Primitive|Item + public function nullableItem(mixed $data, callable|self $transformer, null|string $resourceKey = null): Primitive|Item { - if (is_null($data)) { + if (\is_null($data)) { return $this->primitive(null); } return $this->item($data, $transformer, $resourceKey); } - public function item($data, $transformer, string|null $resourceKey = null): Item + /** @inheritDoc */ + #[\Override] + public function item($data, $transformer, null|string $resourceKey = null): Item { return new Item($data, $transformer, $resourceKey); } - public function collection($data, $transformer, string|null $resourceKey = null): Collection + /** @inheritDoc */ + #[\Override] + public function collection($data, $transformer, null|string $resourceKey = null): Collection { return new Collection($data, $transformer, $resourceKey); } diff --git a/src/Core/Values/Value.php b/src/Core/Values/Value.php index 59f57bb04..549278c85 100644 --- a/src/Core/Values/Value.php +++ b/src/Core/Values/Value.php @@ -1,5 +1,7 @@ basePath); + + return (new ApplicationBuilder(self::$instance))->create(); + } + + public static function configure(null|string $basePath = null): ApplicationBuilder { if (isset(self::$instance)) { return new ApplicationBuilder(self::$instance); } $basePath = match (true) { - is_string($basePath) => $basePath, - default => self::inferBasePath(), + \is_string($basePath) => $basePath, + default => self::inferBasePath(), }; self::$instance = new self($basePath); @@ -64,28 +97,10 @@ public static function inferBasePath(): string { return match (true) { isset($_ENV['APP_BASE_PATH']) => $_ENV['APP_BASE_PATH'], - default => dirname(array_keys(ClassLoader::getRegisteredLoaders())[0]), + default => self::findShortestVendorPath(), }; } - /** - * Get the singleton instance of the class. - */ - public static function instance(): self - { - return self::$instance; - } - - /** - * Reset the configured instance to its default state. - */ - public static function reset(): self - { - self::$instance = new self(self::$instance->basePath); - - return (new ApplicationBuilder(self::$instance))->create(); - } - public function basePath(): string { return $this->basePath; @@ -96,7 +111,7 @@ public function basePath(): string */ public function sharedPath(string $path = ''): string { - return join_paths($this->sharedPath ?: app_path('Ship'), $path); + return join_paths($this->sharedPath !== '' && $this->sharedPath !== '0' ? $this->sharedPath : app_path('Ship'), $path); } /** @@ -109,77 +124,77 @@ public function useSharedPath(string $path): self return $this; } - public function withRouting(callable|null $callback = null): self + public function withRouting(null|callable $callback = null): self { $this->routing ??= new Routing(); - if (!is_null($callback)) { + if (!\is_null($callback)) { $callback($this->routing); } return $this; } - public function withFactories(callable|null $callback = null): self + public function withFactories(null|callable $callback = null): self { $this->factory ??= new Factory(); - if (!is_null($callback)) { + if (!\is_null($callback)) { $callback($this->factory); } return $this; } - public function withRepositories(callable|null $callback = null): self + public function withRepositories(null|callable $callback = null): self { $this->repository ??= new Repository(); - if (!is_null($callback)) { + if (!\is_null($callback)) { $callback($this->repository); } return $this; } - public function withViews(callable|null $callback = null): self + public function withViews(null|callable $callback = null): self { $this->view ??= new View(); - if (!is_null($callback)) { + if (!\is_null($callback)) { $callback($this->view); } return $this; } - public function withTranslations(callable|null $callback = null): self + public function withTranslations(null|callable $callback = null): self { $this->localization ??= new Localization(); - if (!is_null($callback)) { + if (!\is_null($callback)) { $callback($this->localization); } return $this; } - public function withSeeders(callable|null $callback = null): self + public function withSeeders(null|callable $callback = null): self { $this->seeding ??= new Seeding(); - if (!is_null($callback)) { + if (!\is_null($callback)) { $callback($this->seeding); } return $this; } - public function withProviders(callable|null $callback = null): self + public function withProviders(null|callable $callback = null): self { $this->provider ??= new Provider(); - if (!is_null($callback)) { + if (!\is_null($callback)) { $callback($this->provider); } @@ -221,7 +236,7 @@ public function withConfigs(string ...$path): void public function configs(): array { return collect($this->configPaths)->flatMap( - static fn (string $path): array => \Safe\glob($path . '/*.php'), + static fn (string $path): array => glob($path . '/*.php'), )->toArray(); } @@ -233,7 +248,7 @@ public function configs(): array public function helpers(): array { return collect($this->helperPaths)->flatMap( - static fn (string $path): array => \Safe\glob($path . '/*.php'), + static fn (string $path): array => glob($path . '/*.php'), )->toArray(); } @@ -337,4 +352,27 @@ public function view(): View { return $this->view; } + + /** + * Find the shortest vendor path, which should be the main project's vendor directory. + * + * TODO: This approach is needed because: + * 1. In CI environments, the order of ClassLoader instances can differ from local/Docker environments + * 2. There can be multiple ClassLoader instances (main project + nested ones like rector/rector) + * 3. When CI runs, sometimes the nested loader from vendor/rector/rector/vendor appears first + * causing base path to be incorrectly resolved to /home/runner/work/core/core/vendor/rector/rector + * 4. By selecting the shortest path, we consistently get the main project's vendor dir + * regardless of loader registration order + */ + private static function findShortestVendorPath(): string + { + $registeredLoaders = ClassLoader::getRegisteredLoaders(); + $vendorPaths = array_keys($registeredLoaders); + + usort($vendorPaths, static function ($a, $b): int { + return \strlen($a) - \strlen($b); + }); + + return \dirname($vendorPaths[0]); + } } diff --git a/src/Foundation/Configuration/ApplicationBuilder.php b/src/Foundation/Configuration/ApplicationBuilder.php index 5e1a10afb..2a9ea15c2 100644 --- a/src/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Foundation/Configuration/ApplicationBuilder.php @@ -1,117 +1,69 @@ withDefaults($this->apiato->basePath()); - } - - private function withDefaults(string $basePath): void + public function __construct(private Apiato $apiato) { - $this->useSharedPath( - $this->joinPaths($basePath, 'app/Ship'), - )->withConfigs( - shared_path('Configs'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Configs')), - )->withEvents( - shared_path('Listeners'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Listeners')), - )->withCommands( - shared_path('Commands'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/CLI/Commands')), - )->withHelpers( - shared_path('Helpers'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Helpers')), - )->withMigrations( - shared_path('Migrations'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Data/Migrations')), - )->withProviders(function (Provider $provider) use ($basePath): void { - $provider->loadFrom( - shared_path('Providers'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Providers')), - ); - })->withSeeders(function (Seeding $seeding) use ($basePath): void { - $seeding->loadFrom( - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Data/Seeders')), - ); - })->withTranslations(function (Localization $localization) use ($basePath): void { - $localization->loadFrom( - shared_path('Languages'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Languages')), - ); - })->withViews(function (View $view) use ($basePath): void { - $view->loadFrom( - shared_path('Views'), - shared_path('Mails/Templates'), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/WEB/Views')), - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Mails/Templates')), - ); - })->withRouting(function (Routing $routing) use ($basePath): void { - $routing->loadApiRoutesFrom( - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/API/Routes')), - )->loadWebRoutesFrom( - ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/WEB/Routes')), - ); - })->withFactories() - ->withRepositories(); + $this->withDefaults($this->apiato->basePath()); } // TODO: remove non-standard Laravel configs like the specific Repository classes we have in Apiato. // Extend the config and add the repository configuration. So anybody can add their own custom configuration. - // Also the same goes for the helpers config I guess? Checkout what other configs we added that could be considered custom and - // non-standard Laravel stuff. - public function withRepositories(callable|null $callback = null): self + // Also the same goes for the helpers config I guess? + // Checkout what other configs we added that could be considered custom and non-standard Laravel stuff. + public function withRepositories(null|callable $callback = null): self { $this->apiato->withRepositories($callback); return $this; } - public function withFactories(callable|null $callback = null): self + public function withFactories(null|callable $callback = null): self { $this->apiato->withFactories($callback); return $this; } - public function withRouting(callable|null $callback = null): self + public function withRouting(null|callable $callback = null): self { $this->apiato->withRouting($callback); return $this; } - public function withViews(callable|null $callback = null): self + public function withViews(null|callable $callback = null): self { $this->apiato->withViews($callback); return $this; } - public function withTranslations(callable|null $callback = null): self + public function withTranslations(null|callable $callback = null): self { $this->apiato->withTranslations($callback); return $this; } - public function withSeeders(callable|null $callback = null): self + public function withSeeders(null|callable $callback = null): self { $this->apiato->withSeeders($callback); return $this; } - public function withProviders(callable|null $callback = null): self + public function withProviders(null|callable $callback = null): self { $this->apiato->withProviders($callback); @@ -163,6 +115,61 @@ public function useSharedPath(string $path): self return $this; } + public function create(): Apiato + { + return $this->apiato; + } + + private function withDefaults(string $basePath): void + { + $this->useSharedPath( + $this->joinPaths($basePath, 'app/Ship'), + )->withConfigs( + shared_path('Configs'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Configs')), + )->withEvents( + shared_path('Listeners'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Listeners')), + )->withCommands( + shared_path('Commands'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/CLI/Commands')), + )->withHelpers( + shared_path('Helpers'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Helpers')), + )->withMigrations( + shared_path('Migrations'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Data/Migrations')), + )->withProviders(function (Provider $provider) use ($basePath): void { + $provider->loadFrom( + shared_path('Providers'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Providers')), + ); + })->withSeeders(function (Seeding $seeding) use ($basePath): void { + $seeding->loadFrom( + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Data/Seeders')), + ); + })->withTranslations(function (Localization $localization) use ($basePath): void { + $localization->loadFrom( + shared_path('Languages'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Languages')), + ); + })->withViews(function (View $view) use ($basePath): void { + $view->loadFrom( + shared_path('Views'), + shared_path('Mails/Templates'), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/WEB/Views')), + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/Mails/Templates')), + ); + })->withRouting(function (Routing $routing) use ($basePath): void { + $routing->loadApiRoutesFrom( + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/API/Routes')), + )->loadWebRoutesFrom( + ...$this->getDirs($this->joinPaths($basePath, 'app/Containers/*/*/UI/WEB/Routes')), + ); + })->withFactories() + ->withRepositories(); + } + private function joinPaths(string $basePath, string $path = ''): string { return join_paths($basePath, $path); @@ -175,14 +182,6 @@ private function joinPaths(string $basePath, string $path = ''): string */ private function getDirs(string $pattern): array { - /** @var string[] $dirs */ - $dirs = \Safe\glob($pattern, GLOB_ONLYDIR | GLOB_NOSORT); - - return $dirs; - } - - public function create(): Apiato - { - return $this->apiato; + return glob($pattern, GLOB_ONLYDIR | GLOB_NOSORT); } } diff --git a/src/Foundation/Configuration/Factory.php b/src/Foundation/Configuration/Factory.php index 27752d867..60ec9312f 100644 --- a/src/Foundation/Configuration/Factory.php +++ b/src/Foundation/Configuration/Factory.php @@ -1,5 +1,7 @@ resolveFactoryNameUsing( - static function (string $modelName): string|null { + static function (string $modelName): null|string { $factoryName = Str::of($modelName)->beforeLast('Models\\') ->append('Data\\Factories\\' . class_basename($modelName) . 'Factory') ->value(); @@ -46,7 +48,7 @@ public function resolveFactoryNameUsing(\Closure $callback): self * * @return class-string|null */ - public function resolveFactoryName(string $modelName): string|null + public function resolveFactoryName(string $modelName): null|string { return app()->call(self::$nameResolver, ['modelName' => $modelName]); } diff --git a/src/Foundation/Configuration/Localization.php b/src/Foundation/Configuration/Localization.php index 6fb69f153..894229c11 100644 --- a/src/Foundation/Configuration/Localization.php +++ b/src/Foundation/Configuration/Localization.php @@ -1,18 +1,21 @@ buildNamespaceUsing(function (string $path): string { + $this->buildNamespaceUsing(static function (string $path): string { if (Str::contains($path, shared_path())) { return Str::of(shared_path()) ->afterLast(DIRECTORY_SEPARATOR) diff --git a/src/Foundation/Configuration/Provider.php b/src/Foundation/Configuration/Provider.php index 791c10d14..af5d24ada 100644 --- a/src/Foundation/Configuration/Provider.php +++ b/src/Foundation/Configuration/Provider.php @@ -1,5 +1,7 @@ []|null $providers */ - public function __construct(array|null $providers = null) + public function __construct(null|array $providers = null) { parent::__construct($providers ?? DefaultProviders::providers()); } @@ -30,12 +32,12 @@ public function toArray(): array public function loadFrom(string ...$paths): self { - $classMapper = new ClassMapGenerator(); + $classMapGenerator = new ClassMapGenerator(); foreach ($paths as $path) { - $classMapper->scanPaths($path); + $classMapGenerator->scanPaths($path); } - $this->merge(array_keys($classMapper->getClassMap()->getMap())); + $this->merge(array_keys($classMapGenerator->getClassMap()->getMap())); return $this; } diff --git a/src/Foundation/Configuration/Repository.php b/src/Foundation/Configuration/Repository.php index 7682e8720..62817a957 100644 --- a/src/Foundation/Configuration/Repository.php +++ b/src/Foundation/Configuration/Repository.php @@ -1,5 +1,7 @@ append('Models\\') ->append( Str::of(class_basename($repositoryName)) - ->beforeLast('Repository') - ->value(), + ->beforeLast('Repository') + ->value(), )->value(); if (class_exists($modelName)) { return $modelName; } - throw new \RuntimeException("Model not found for repository: {$repositoryName}"); + throw new \RuntimeException('Model not found for repository: ' . $repositoryName); }, ); } diff --git a/src/Foundation/Configuration/Routing.php b/src/Foundation/Configuration/Routing.php index 8ab0de27e..e745af2fa 100644 --- a/src/Foundation/Configuration/Routing.php +++ b/src/Foundation/Configuration/Routing.php @@ -1,5 +1,7 @@ apiVersionAutoPrefix) { - return $this->apiPrefix . $this->resolveApiVersionFor($file); - } - - return $this->apiPrefix; - } - - private function resolveApiVersionFor(string $file): string - { - return app()->call(self::$apiVersionResolver, ['file' => $file]); - } - public function getApiPrefix(): string { return $this->apiPrefix; @@ -119,4 +98,32 @@ public function webRoutes(): array ->flatMap(static fn (string $path): array => recursiveGlob($path . '/*.php')) ->toArray(); } + + /** + * @return string[] + */ + private function getApiMiddlewares(): array + { + $middlewares = ['api']; + + if (config('apiato.api.rate-limiter.enabled')) { + $middlewares[] = 'throttle:' . config('apiato.api.rate-limiter.name'); + } + + return $middlewares; + } + + private function buildApiPrefixFor(string $file): string + { + if ($this->apiVersionAutoPrefix) { + return $this->apiPrefix . $this->resolveApiVersionFor($file); + } + + return $this->apiPrefix; + } + + private function resolveApiVersionFor(string $file): string + { + return app()->call(self::$apiVersionResolver, ['file' => $file]); + } } diff --git a/src/Foundation/Configuration/Seeding.php b/src/Foundation/Configuration/Seeding.php index 9e8c77624..b34e0325f 100644 --- a/src/Foundation/Configuration/Seeding.php +++ b/src/Foundation/Configuration/Seeding.php @@ -1,5 +1,7 @@ getSortedFiles($classMapGroupedByDirectory); } + public function loadFrom(string ...$paths): self + { + $this->paths = $paths; + + return $this; + } + /** * @param array, non-empty-string>> $classMapGroupedByDirectory * @@ -66,11 +76,4 @@ private function getSortedFiles(array $classMapGroupedByDirectory): array { return app()->call(self::$seederSorter, ['classMapGroupedByDirectory' => $classMapGroupedByDirectory]); } - - public function loadFrom(string ...$paths): self - { - $this->paths = $paths; - - return $this; - } } diff --git a/src/Foundation/Configuration/View.php b/src/Foundation/Configuration/View.php index ffa825dcd..856b9c664 100644 --- a/src/Foundation/Configuration/View.php +++ b/src/Foundation/Configuration/View.php @@ -1,18 +1,21 @@ buildNamespaceUsing(function (string $path): string { + $this->buildNamespaceUsing(static function (string $path): string { if (Str::contains($path, shared_path())) { return Str::of(shared_path()) ->afterLast(DIRECTORY_SEPARATOR) diff --git a/src/Foundation/Database/DatabaseSeeder.php b/src/Foundation/Database/DatabaseSeeder.php index 3efc67162..8f47dbdfa 100644 --- a/src/Foundation/Database/DatabaseSeeder.php +++ b/src/Foundation/Database/DatabaseSeeder.php @@ -1,9 +1,12 @@ seeding()->seeders(); - collect($classes)->each(fn (string $class) => $this->call($class)); + /** + * @var class-string $class + * @var Seeder $this + */ + collect($classes)->each(fn (string $class) => $class::WITH_TRANSACTIONS + ? DB::transaction(fn (string $class) => $this->call($class)) + : $this->call($class)); } } diff --git a/src/Foundation/Providers/ApiatoServiceProvider.php b/src/Foundation/Providers/ApiatoServiceProvider.php index f29e8d2ce..75dba76c7 100644 --- a/src/Foundation/Providers/ApiatoServiceProvider.php +++ b/src/Foundation/Providers/ApiatoServiceProvider.php @@ -1,5 +1,7 @@ app->singletonIf(Apiato::class, static fn (): Apiato => Apiato::instance()); - $this->app->extend('hashids', static function (HashidsManager $manager) { + $this->app->extend('hashids', static function (HashidsManager $manager): HashidsManagerDecorator { return new HashidsManagerDecorator($manager); }); } diff --git a/src/Foundation/Support/Providers/ConfigServiceProvider.php b/src/Foundation/Support/Providers/ConfigServiceProvider.php index 71fab0da0..f71ad6e55 100644 --- a/src/Foundation/Support/Providers/ConfigServiceProvider.php +++ b/src/Foundation/Support/Providers/ConfigServiceProvider.php @@ -1,5 +1,7 @@ localization(); - foreach ($configuration->paths() as $path) { - $this->loadTranslationsFrom($path, $configuration->buildNamespaceFor($path)); + $localization = $apiato->localization(); + foreach ($localization->paths() as $path) { + $this->loadTranslationsFrom($path, $localization->buildNamespaceFor($path)); $this->loadJsonTranslationsFrom($path); } } diff --git a/src/Foundation/Support/Providers/MigrationServiceProvider.php b/src/Foundation/Support/Providers/MigrationServiceProvider.php index 97a43a99b..abc705f40 100644 --- a/src/Foundation/Support/Providers/MigrationServiceProvider.php +++ b/src/Foundation/Support/Providers/MigrationServiceProvider.php @@ -1,5 +1,7 @@ view(); - foreach ($configuration->paths() as $path) { - $this->loadViewsFrom($path, $configuration->buildNamespaceFor($path)); + $view = $apiato->view(); + foreach ($view->paths() as $path) { + $this->loadViewsFrom($path, $view->buildNamespaceFor($path)); } } } diff --git a/src/Foundation/helpers.php b/src/Foundation/helpers.php index 2d8832dd4..4149d0bcf 100644 --- a/src/Foundation/helpers.php +++ b/src/Foundation/helpers.php @@ -1,9 +1,13 @@ checkParameterOrAsk('model', 'Enter the name of the model this action is for.', $this->containerName); $ui = Str::upper($this->checkParameterOrChoice('ui', 'Which UI is this Action for?', ['API', 'WEB'], 0)); @@ -68,18 +76,18 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - 'models' => $models, - 'ui' => $ui, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + 'models' => $models, + 'ui' => $ui, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -90,6 +98,7 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return 'DefaultAction'; diff --git a/src/Generator/Commands/ConfigurationGenerator.php b/src/Generator/Commands/ConfigurationGenerator.php index cd307b200..0bb6a4abd 100644 --- a/src/Generator/Commands/ConfigurationGenerator.php +++ b/src/Generator/Commands/ConfigurationGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -66,6 +74,7 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return Str::camel($this->sectionName) . '-' . Str::camel($this->containerName); diff --git a/src/Generator/Commands/ContainerApiGenerator.php b/src/Generator/Commands/ContainerApiGenerator.php index 1575bddb8..7b13cf2a9 100644 --- a/src/Generator/Commands/ContainerApiGenerator.php +++ b/src/Generator/Commands/ContainerApiGenerator.php @@ -1,15 +1,20 @@ sectionName; - $_sectionName = Str::lower($this->sectionName); - - $containerName = $this->containerName; - $_containerName = Str::lower($this->containerName); - - $model = $this->containerName; - $models = Pluralizer::plural($model); - - $this->printInfoMessage('Generating README File'); - $this->call('apiato:make:readme', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => 'README', - ]); - - $this->printInfoMessage('Generating Configuration File'); - $this->call('apiato:make:configuration', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => Str::camel($this->sectionName) . '-' . Str::camel($this->containerName), - ]); - - $this->printInfoMessage('Generating Model and Repository'); - $this->call('apiato:make:model', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => $model, - '--repository' => true, - ]); - - $this->printInfoMessage('Generating a basic Migration file'); - $this->call('apiato:make:migration', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => 'create_' . Str::snake($models) . '_table', - '--tablename' => Str::snake($models), - ]); + [ + $sectionName, + $_sectionName, + $containerName, + $_containerName, + $model, + $models, + ] = $this->runCallParam(); $this->printInfoMessage('Generating Transformer for the Model'); $this->call('apiato:make:transformer', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $containerName . 'Transformer', - '--model' => $model, - '--full' => false, + '--file' => $containerName . 'Transformer', + '--model' => $model, + '--full' => false, ]); $this->printInfoMessage('Generating Factory for the Model'); $this->call('apiato:make:factory', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $containerName . 'Factory', - '--model' => $model, + '--file' => $containerName . 'Factory', + '--model' => $model, ]); $this->printInfoMessage('Generating Default Routes'); - $version = $this->checkParameterOrAsk('docversion', 'Enter the version for all API endpoints (integer)', 1); + $version = $this->checkParameterOrAsk('docversion', 'Enter the version for all API endpoints (integer)', '1'); $doctype = $this->checkParameterOrChoice('doctype', 'Select the type for all API endpoints', ['private', 'public'], 0); - // get the URI and remove the first trailing slash + // Get the URI and remove the first trailing slash $url = Str::lower($this->checkParameterOrAsk('url', 'Enter the base URI for all API endpoints (foo/bar/{id})', Str::kebab($models))); $url = ltrim($url, '/'); - $controllertype = Str::lower($this->checkParameterOrChoice('controllertype', 'Select the controller type (Single or Multi Action Controller)', ['SAC', 'MAC'], 0)); + $controllerType = Str::lower($this->checkParameterOrChoice('controllertype', 'Select the controller type (Single or Multi Action Controller)', ['SAC', 'MAC'], 0)); $generateEvents = $this->checkParameterOrConfirm('events', 'Do you want to generate the corresponding CRUD Events for this Container?', false); $generateListeners = false; + if ($generateEvents) { $generateListeners = $this->checkParameterOrConfirm('listeners', 'Do you want to generate the corresponding Event Listeners for this Events?', false); } + $generateTests = $this->checkParameterOrConfirm('tests', 'Do you want to generate the corresponding Tests for this Container?', true); $generateEvents ?: $this->printInfoMessage('Generating CRUD Events'); $generateTests ?: $this->printInfoMessage('Generating Tests for Container'); + $this->printInfoMessage('Generating Requests for Routes'); $this->printInfoMessage('Generating Default Actions'); $this->printInfoMessage('Generating Default Tasks'); @@ -140,236 +124,236 @@ public function getUserInputs(): array|null $events = []; $routes = [ [ - 'stub' => 'List', - 'name' => 'List' . $models, + 'stub' => 'List', + 'name' => 'List' . $models, 'operation' => 'list', - 'verb' => 'GET', - 'url' => $url, - 'action' => 'List' . $models . 'Action', - 'request' => 'List' . $models . 'Request', - 'task' => 'List' . $models . 'Task', - 'unittest' => [ + 'verb' => 'GET', + 'url' => $url, + 'action' => 'List' . $models . 'Action', + 'request' => 'List' . $models . 'Request', + 'task' => 'List' . $models . 'Task', + 'unittest' => [ 'task' => [ 'stubfoldername' => 'tasks', - 'foldername' => 'Tasks', - 'filename' => 'List' . $models . 'TaskTest', + 'foldername' => 'Tasks', + 'filename' => 'List' . $models . 'TaskTest', ], ], 'functionaltest' => 'List' . $models . 'Test', - 'event' => $models . 'Listed', - 'controller' => 'List' . $models . 'Controller', + 'event' => $models . 'Listed', + 'controller' => 'List' . $models . 'Controller', ], [ - 'stub' => 'Find', - 'name' => 'Find' . $model . 'ById', + 'stub' => 'Find', + 'name' => 'Find' . $model . 'ById', 'operation' => 'findById', - 'verb' => 'GET', - 'url' => $url . '/{id}', - 'action' => 'Find' . $model . 'ByIdAction', - 'request' => 'Find' . $model . 'ByIdRequest', - 'task' => 'Find' . $model . 'ByIdTask', - 'unittest' => [ + 'verb' => 'GET', + 'url' => $url . '/{id}', + 'action' => 'Find' . $model . 'ByIdAction', + 'request' => 'Find' . $model . 'ByIdRequest', + 'task' => 'Find' . $model . 'ByIdTask', + 'unittest' => [ 'task' => [ 'stubfoldername' => 'tasks', - 'foldername' => 'Tasks', - 'filename' => 'Find' . $model . 'ByIdTaskTest', + 'foldername' => 'Tasks', + 'filename' => 'Find' . $model . 'ByIdTaskTest', ], ], 'functionaltest' => 'Find' . $model . 'ByIdTest', - 'event' => $model . 'Requested', - 'controller' => 'Find' . $model . 'ByIdController', + 'event' => $model . 'Requested', + 'controller' => 'Find' . $model . 'ByIdController', ], [ - 'stub' => 'Create', - 'name' => 'Create' . $model, + 'stub' => 'Create', + 'name' => 'Create' . $model, 'operation' => 'create', - 'verb' => 'POST', - 'url' => $url, - 'action' => 'Create' . $model . 'Action', - 'request' => 'Create' . $model . 'Request', - 'task' => 'Create' . $model . 'Task', - 'unittest' => [ + 'verb' => 'POST', + 'url' => $url, + 'action' => 'Create' . $model . 'Action', + 'request' => 'Create' . $model . 'Request', + 'task' => 'Create' . $model . 'Task', + 'unittest' => [ 'task' => [ 'stubfoldername' => 'tasks', - 'foldername' => 'Tasks', - 'filename' => 'Create' . $model . 'TaskTest', + 'foldername' => 'Tasks', + 'filename' => 'Create' . $model . 'TaskTest', ], ], 'functionaltest' => 'Create' . $model . 'Test', - 'event' => $model . 'Created', - 'controller' => 'Create' . $model . 'Controller', + 'event' => $model . 'Created', + 'controller' => 'Create' . $model . 'Controller', ], [ - 'stub' => 'Update', - 'name' => 'Update' . $model, + 'stub' => 'Update', + 'name' => 'Update' . $model, 'operation' => 'update', - 'verb' => 'PATCH', - 'url' => $url . '/{id}', - 'action' => 'Update' . $model . 'Action', - 'request' => 'Update' . $model . 'Request', - 'task' => 'Update' . $model . 'Task', - 'unittest' => [ + 'verb' => 'PATCH', + 'url' => $url . '/{id}', + 'action' => 'Update' . $model . 'Action', + 'request' => 'Update' . $model . 'Request', + 'task' => 'Update' . $model . 'Task', + 'unittest' => [ 'task' => [ 'stubfoldername' => 'tasks', - 'foldername' => 'Tasks', - 'filename' => 'Update' . $model . 'TaskTest', + 'foldername' => 'Tasks', + 'filename' => 'Update' . $model . 'TaskTest', ], ], 'functionaltest' => 'Update' . $model . 'Test', - 'event' => $model . 'Updated', - 'controller' => 'Update' . $model . 'Controller', + 'event' => $model . 'Updated', + 'controller' => 'Update' . $model . 'Controller', ], [ - 'stub' => 'Delete', - 'name' => 'Delete' . $model, + 'stub' => 'Delete', + 'name' => 'Delete' . $model, 'operation' => 'delete', - 'verb' => 'DELETE', - 'url' => $url . '/{id}', - 'action' => 'Delete' . $model . 'Action', - 'request' => 'Delete' . $model . 'Request', - 'task' => 'Delete' . $model . 'Task', - 'unittest' => [ + 'verb' => 'DELETE', + 'url' => $url . '/{id}', + 'action' => 'Delete' . $model . 'Action', + 'request' => 'Delete' . $model . 'Request', + 'task' => 'Delete' . $model . 'Task', + 'unittest' => [ 'task' => [ 'stubfoldername' => 'tasks', - 'foldername' => 'Tasks', - 'filename' => 'Delete' . $model . 'TaskTest', + 'foldername' => 'Tasks', + 'filename' => 'Delete' . $model . 'TaskTest', ], ], 'functionaltest' => 'Delete' . $model . 'Test', - 'event' => $model . 'Deleted', - 'controller' => 'Delete' . $model . 'Controller', + 'event' => $model . 'Deleted', + 'controller' => 'Delete' . $model . 'Controller', ], ]; foreach ($routes as $route) { $this->call('apiato:make:request', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['request'], - '--ui' => $ui, - '--stub' => $route['stub'], + '--file' => $route['request'], + '--ui' => $ui, + '--stub' => $route['stub'], ]); $this->call('apiato:make:action', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['action'], - '--ui' => $ui, - '--model' => $model, - '--stub' => $route['stub'], + '--file' => $route['action'], + '--ui' => $ui, + '--model' => $model, + '--stub' => $route['stub'], ]); $this->call('apiato:make:task', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['task'], - '--model' => $model, - '--stub' => $route['stub'], - '--event' => $generateEvents ? $route['event'] : false, + '--file' => $route['task'], + '--model' => $model, + '--stub' => $route['stub'], + '--event' => $generateEvents ? $route['event'] : false, ]); if ($generateEvents) { $this->call('apiato:make:event', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['event'], - '--model' => $model, - '--stub' => $route['stub'], - '--listener' => false, + '--file' => $route['event'], + '--model' => $model, + '--stub' => $route['stub'], + '--listener' => false, ]); $events[] = $route['event']; } if ($generateTests) { $this->call('apiato:make:test:unit', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => $route['unittest']['task']['filename'], + '--section' => $sectionName, + '--container' => $containerName, + '--file' => $route['unittest']['task']['filename'], '--stubfoldername' => $route['unittest']['task']['stubfoldername'], - '--foldername' => $route['unittest']['task']['foldername'], - '--model' => $model, - '--stub' => $route['stub'], - '--event' => $generateEvents ? $route['event'] : false, + '--foldername' => $route['unittest']['task']['foldername'], + '--model' => $model, + '--stub' => $route['stub'], + '--event' => $generateEvents ? $route['event'] : false, ]); $this->call('apiato:make:test:unit', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => $model . 'FactoryTest', + '--section' => $sectionName, + '--container' => $containerName, + '--file' => $model . 'FactoryTest', '--foldername' => 'Factories', - '--model' => $model, - '--stub' => 'factory', - '--event' => false, + '--model' => $model, + '--stub' => 'factory', + '--event' => false, ]); $this->call('apiato:make:test:unit', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => $models . 'MigrationTest', + '--section' => $sectionName, + '--container' => $containerName, + '--file' => $models . 'MigrationTest', '--stubfoldername' => 'data', - '--foldername' => 'Data/Migrations', - '--model' => $model, - '--stub' => 'migration', - '--event' => false, - '--tablename' => Str::snake(Pluralizer::plural($containerName)), + '--foldername' => 'Data/Migrations', + '--model' => $model, + '--stub' => 'migration', + '--event' => false, + '--tablename' => Str::snake(Pluralizer::plural($containerName)), ]); $this->call('apiato:make:test:functional', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['functionaltest'], - '--model' => $model, - '--ui' => $ui, - '--stub' => $route['stub'], - '--url' => $route['url'], + '--file' => $route['functionaltest'], + '--model' => $model, + '--ui' => $ui, + '--stub' => $route['stub'], + '--url' => $route['url'], ]); } $routeArgs = [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => $route['name'], - '--ui' => $ui, - '--operation' => $route['operation'], - '--doctype' => $doctype, + '--section' => $sectionName, + '--container' => $containerName, + '--file' => $route['name'], + '--ui' => $ui, + '--operation' => $route['operation'], + '--doctype' => $doctype, '--docversion' => $version, - '--url' => $route['url'], - '--verb' => $route['verb'], + '--url' => $route['url'], + '--verb' => $route['verb'], ]; - if ('sac' === $controllertype) { + if ($controllerType === 'sac') { $this->call('apiato:make:route', [ ...$routeArgs, '--controller' => $route['controller'], - '--sac' => true, + '--sac' => true, ]); $this->call('apiato:make:controller', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['controller'], - '--model' => $model, - '--ui' => $ui, - '--stub' => $route['stub'], + '--file' => $route['controller'], + '--model' => $model, + '--ui' => $ui, + '--stub' => $route['stub'], ]); } else { $this->call('apiato:make:route', [ ...$routeArgs, '--controller' => 'Controller', - '--sac' => false, + '--sac' => false, ]); } } - if ('mac' === $controllertype) { + if ($controllerType === 'mac') { $this->printInfoMessage('Generating Controller to wire everything together'); $this->call('apiato:make:controller', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--model' => $model, - '--file' => 'Controller', - '--ui' => $ui, - '--stub' => 'crud', + '--model' => $model, + '--file' => 'Controller', + '--ui' => $ui, + '--stub' => 'crud', ]); } @@ -378,33 +362,33 @@ public function getUserInputs(): array|null foreach ($events as $event) { $listener = $event . 'Listener'; $this->call('apiato:make:listener', [ - '--section' => $this->sectionName, + '--section' => $this->sectionName, '--container' => $this->containerName, - '--file' => $listener, - '--event' => $event, + '--file' => $listener, + '--event' => $event, ]); } } $this->printInfoMessage('Generating ServiceProvider'); $this->call('apiato:make:provider', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => Str::title($this->containerName) . 'ServiceProvider', - '--stub' => 'service-provider', + '--file' => Str::title($this->containerName) . 'ServiceProvider', + '--stub' => 'service-provider', ]); $generateComposerFile = [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => $_sectionName, - 'section-name' => $this->sectionName, + '_section-name' => $_sectionName, + 'section-name' => $this->sectionName, '_container-name' => $_containerName, - 'container-name' => $containerName, - 'class-name' => $this->fileName, + 'container-name' => $containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -417,14 +401,16 @@ public function getUserInputs(): array|null return $generateComposerFile; } - return null; + return []; } + #[\Override] public function getDefaultFileName(): string { return 'composer'; } + #[\Override] public function getDefaultFileExtension(): string { return 'json'; diff --git a/src/Generator/Commands/ContainerGenerator.php b/src/Generator/Commands/ContainerGenerator.php index 046ce6fda..50e8f7c99 100644 --- a/src/Generator/Commands/ContainerGenerator.php +++ b/src/Generator/Commands/ContainerGenerator.php @@ -1,5 +1,7 @@ checkParameterOrChoice('ui', 'Select the UI for this container', ['API', 'WEB', 'BOTH'], 0)); $generateEvents = $this->checkParameterOrConfirm('events', 'Do you want to generate the corresponding CRUD Events for this Container?', false); $generateListeners = false; + if ($generateEvents) { $generateListeners = $this->checkParameterOrConfirm('listeners', 'Do you want to generate the corresponding Event Listeners for this Events?', false); } + $generateTests = $this->checkParameterOrConfirm('tests', 'Do you want to generate the corresponding Tests for this Container?', true); + if ($generateTests) { $this->call('apiato:make:test:testcase', [ - '--section' => $this->sectionName, + '--section' => $this->sectionName, '--container' => $this->containerName, - '--file' => 'TestCase', - '--type' => 'container', + '--file' => 'TestCase', + '--type' => 'container', ]); $this->call('apiato:make:test:testcase', [ - '--section' => $this->sectionName, + '--section' => $this->sectionName, '--container' => $this->containerName, - '--file' => 'TestCase', - '--type' => 'unit', + '--file' => 'TestCase', + '--type' => 'unit', ]); $this->call('apiato:make:test:testcase', [ - '--section' => $this->sectionName, + '--section' => $this->sectionName, '--container' => $this->containerName, - '--file' => 'TestCase', - '--type' => 'functional', + '--file' => 'TestCase', + '--type' => 'functional', ]); // $this->call('apiato:make:test:testcase', [ // '--section' => $this->sectionName, @@ -83,10 +94,10 @@ public function getUserInputs(): array|null // '--type' => 'e2e', // ]); $this->call('apiato:make:test:testcase', [ - '--section' => $this->sectionName, + '--section' => $this->sectionName, '--container' => $this->containerName, - '--file' => 'TestCase', - '--type' => 'api', + '--file' => 'TestCase', + '--type' => 'api', ]); // $this->call('apiato:make:test:testcase', [ // '--section' => $this->sectionName, @@ -107,23 +118,23 @@ public function getUserInputs(): array|null $sectionName = $this->sectionName; $_sectionName = Str::lower($this->sectionName); - if ('api' === $ui || 'both' === $ui) { + if ($ui === 'api' || $ui === 'both') { $this->call('apiato:make:container:api', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => 'composer', - '--events' => $generateEvents, - '--listeners' => $generateListeners, - '--tests' => $generateTests, + '--section' => $sectionName, + '--container' => $containerName, + '--file' => 'composer', + '--events' => $generateEvents, + '--listeners' => $generateListeners, + '--tests' => $generateTests, '--maincalled' => true, ]); } - if ('web' === $ui || 'both' === $ui) { + if ($ui === 'web' || $ui === 'both') { $this->call('apiato:make:container:web', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => 'composer', + '--section' => $sectionName, + '--container' => $containerName, + '--file' => 'composer', '--maincalled' => true, ]); } @@ -132,15 +143,15 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => $_sectionName, - 'section-name' => $this->sectionName, + '_section-name' => $_sectionName, + 'section-name' => $this->sectionName, '_container-name' => $_containerName, - 'container-name' => $containerName, - 'class-name' => $this->fileName, + 'container-name' => $containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -151,11 +162,13 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return 'composer'; } + #[\Override] public function getDefaultFileExtension(): string { return 'json'; diff --git a/src/Generator/Commands/ContainerWebGenerator.php b/src/Generator/Commands/ContainerWebGenerator.php index 4763d8ac2..571a196cb 100644 --- a/src/Generator/Commands/ContainerWebGenerator.php +++ b/src/Generator/Commands/ContainerWebGenerator.php @@ -1,15 +1,19 @@ sectionName; - $_sectionName = Str::lower($this->sectionName); - - $containerName = $this->containerName; - $_containerName = Str::lower($this->containerName); - - $model = $this->containerName; - $models = Pluralizer::plural($model); - - $this->printInfoMessage('Generating README File'); - $this->call('apiato:make:readme', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => 'README', - ]); - - $this->printInfoMessage('Generating Configuration File'); - $this->call('apiato:make:configuration', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => Str::camel($this->sectionName) . '-' . Str::camel($this->containerName), - ]); + [ + $sectionName, + $_sectionName, + $containerName, + $_containerName, + $model, + $models, + ] = $this->runCallParam(); $this->printInfoMessage('Generating ServiceProvider'); $this->call('apiato:make:provider', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => Str::title($this->containerName) . 'ServiceProvider', - '--stub' => 'service-provider', - ]); - - $this->printInfoMessage('Generating Model and Repository'); - $this->call('apiato:make:model', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => $model, - '--repository' => true, - ]); - - $this->printInfoMessage('Generating a basic Migration file'); - $this->call('apiato:make:migration', [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => 'create_' . Str::snake($models) . '_table', - '--tablename' => Str::snake($models), + '--file' => Str::title($this->containerName) . 'ServiceProvider', + '--stub' => 'service-provider', ]); $this->printInfoMessage('Generating Default Routes'); @@ -115,173 +95,173 @@ public function getUserInputs(): array|null $routes = [ [ - 'stub' => 'List', - 'name' => 'List' . $models, - 'operation' => 'index', - 'verb' => 'GET', - 'url' => $url, - 'action' => 'List' . $models . 'Action', - 'request' => 'List' . $models . 'Request', - 'task' => 'List' . $models . 'Task', + 'stub' => 'List', + 'name' => 'List' . $models, + 'operation' => 'index', + 'verb' => 'GET', + 'url' => $url, + 'action' => 'List' . $models . 'Action', + 'request' => 'List' . $models . 'Request', + 'task' => 'List' . $models . 'Task', 'controller' => 'List' . $models . 'Controller', ], [ - 'stub' => 'Find', - 'name' => 'Find' . $model . 'ById', - 'operation' => 'show', - 'verb' => 'GET', - 'url' => $url . '/{id}', - 'action' => 'Find' . $model . 'ByIdAction', - 'request' => 'Find' . $model . 'ByIdRequest', - 'task' => 'Find' . $model . 'ByIdTask', + 'stub' => 'Find', + 'name' => 'Find' . $model . 'ById', + 'operation' => 'show', + 'verb' => 'GET', + 'url' => $url . '/{id}', + 'action' => 'Find' . $model . 'ByIdAction', + 'request' => 'Find' . $model . 'ByIdRequest', + 'task' => 'Find' . $model . 'ByIdTask', 'controller' => 'Find' . $model . 'ByIdController', ], [ - 'stub' => 'Store', - 'name' => 'Store' . $model, - 'operation' => 'store', - 'verb' => 'POST', - 'url' => $url . '/store', - 'action' => 'Create' . $model . 'Action', - 'request' => 'Store' . $model . 'Request', - 'task' => 'Create' . $model . 'Task', + 'stub' => 'Store', + 'name' => 'Store' . $model, + 'operation' => 'store', + 'verb' => 'POST', + 'url' => $url . '/store', + 'action' => 'Create' . $model . 'Action', + 'request' => 'Store' . $model . 'Request', + 'task' => 'Create' . $model . 'Task', 'controller' => 'Store' . $model . 'Controller', ], [ - 'stub' => 'Create', - 'name' => 'Create' . $model, - 'operation' => 'create', - 'verb' => 'GET', - 'url' => $url . '/create', - 'action' => null, - 'request' => 'Create' . $model . 'Request', - 'task' => null, + 'stub' => 'Create', + 'name' => 'Create' . $model, + 'operation' => 'create', + 'verb' => 'GET', + 'url' => $url . '/create', + 'action' => null, + 'request' => 'Create' . $model . 'Request', + 'task' => null, 'controller' => 'Create' . $model . 'Controller', ], [ - 'stub' => 'Update', - 'name' => 'Update' . $model, - 'operation' => 'update', - 'verb' => 'PATCH', - 'url' => $url . '/{id}', - 'action' => 'Update' . $model . 'Action', - 'request' => 'Update' . $model . 'Request', - 'task' => 'Update' . $model . 'Task', + 'stub' => 'Update', + 'name' => 'Update' . $model, + 'operation' => 'update', + 'verb' => 'PATCH', + 'url' => $url . '/{id}', + 'action' => 'Update' . $model . 'Action', + 'request' => 'Update' . $model . 'Request', + 'task' => 'Update' . $model . 'Task', 'controller' => 'Update' . $model . 'Controller', ], [ - 'stub' => 'Edit', - 'name' => 'Edit' . $model, - 'operation' => 'edit', - 'verb' => 'GET', - 'url' => $url . '/{id}/edit', - 'action' => null, - 'request' => 'Edit' . $model . 'Request', - 'task' => null, + 'stub' => 'Edit', + 'name' => 'Edit' . $model, + 'operation' => 'edit', + 'verb' => 'GET', + 'url' => $url . '/{id}/edit', + 'action' => null, + 'request' => 'Edit' . $model . 'Request', + 'task' => null, 'controller' => 'Edit' . $model . 'Controller', ], [ - 'stub' => 'Delete', - 'name' => 'Delete' . $model, - 'operation' => 'destroy', - 'verb' => 'DELETE', - 'url' => $url . '/{id}', - 'action' => 'Delete' . $model . 'Action', - 'request' => 'Delete' . $model . 'Request', - 'task' => 'Delete' . $model . 'Task', + 'stub' => 'Delete', + 'name' => 'Delete' . $model, + 'operation' => 'destroy', + 'verb' => 'DELETE', + 'url' => $url . '/{id}', + 'action' => 'Delete' . $model . 'Action', + 'request' => 'Delete' . $model . 'Request', + 'task' => 'Delete' . $model . 'Task', 'controller' => 'Delete' . $model . 'Controller', ], ]; foreach ($routes as $route) { $this->call('apiato:make:request', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['request'], - '--ui' => $ui, - '--stub' => $route['stub'], + '--file' => $route['request'], + '--ui' => $ui, + '--stub' => $route['stub'], ]); - if (null !== $route['action']) { + if ($route['action'] !== null) { $this->call('apiato:make:action', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['action'], - '--ui' => $ui, - '--model' => $model, - '--stub' => $route['stub'], + '--file' => $route['action'], + '--ui' => $ui, + '--model' => $model, + '--stub' => $route['stub'], ]); } - if (null !== $route['task']) { + if ($route['task'] !== null) { $this->call('apiato:make:task', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['task'], - '--model' => $model, - '--stub' => $route['stub'], + '--file' => $route['task'], + '--model' => $model, + '--stub' => $route['stub'], ]); } $routeArgs = [ - '--section' => $sectionName, - '--container' => $containerName, - '--file' => $route['name'], - '--ui' => $ui, - '--operation' => $route['operation'], - '--doctype' => $doctype, + '--section' => $sectionName, + '--container' => $containerName, + '--file' => $route['name'], + '--ui' => $ui, + '--operation' => $route['operation'], + '--doctype' => $doctype, '--docversion' => $version, - '--url' => $route['url'], - '--verb' => $route['verb'], + '--url' => $route['url'], + '--verb' => $route['verb'], ]; - if ('sac' === $controllertype) { + if ($controllertype === 'sac') { $this->call('apiato:make:route', [ ...$routeArgs, '--controller' => $route['controller'], - '--sac' => true, + '--sac' => true, ]); $this->call('apiato:make:controller', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => $route['controller'], - '--model' => $model, - '--ui' => $ui, - '--stub' => $route['stub'], + '--file' => $route['controller'], + '--model' => $model, + '--ui' => $ui, + '--stub' => $route['stub'], ]); } else { $this->call('apiato:make:route', [ ...$routeArgs, '--controller' => 'Controller', - '--sac' => false, + '--sac' => false, ]); } } - if ('mac' === $controllertype) { + if ($controllertype === 'mac') { $this->printInfoMessage('Generating Controller to wire everything together'); $this->call('apiato:make:controller', [ - '--section' => $sectionName, + '--section' => $sectionName, '--container' => $containerName, - '--file' => 'Controller', - '--model' => $model, - '--ui' => $ui, - '--stub' => 'crud', + '--file' => 'Controller', + '--model' => $model, + '--ui' => $ui, + '--stub' => 'crud', ]); } $generateComposerFile = [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => $_sectionName, - 'section-name' => $this->sectionName, + '_section-name' => $_sectionName, + 'section-name' => $this->sectionName, '_container-name' => $_containerName, - 'container-name' => $containerName, - 'class-name' => $this->fileName, + 'container-name' => $containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -294,14 +274,16 @@ public function getUserInputs(): array|null return $generateComposerFile; } - return null; + return []; } + #[\Override] public function getDefaultFileName(): string { return 'composer'; } + #[\Override] public function getDefaultFileExtension(): string { return 'json'; diff --git a/src/Generator/Commands/ControllerGenerator.php b/src/Generator/Commands/ControllerGenerator.php index 1e46b16f7..60bf35190 100644 --- a/src/Generator/Commands/ControllerGenerator.php +++ b/src/Generator/Commands/ControllerGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('model', 'Enter the name of the Model that this controller uses', $this->containerName); $models = Pluralizer::plural($model); @@ -75,22 +83,22 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, 'user-interface' => Str::upper($ui), ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'user-interface' => Str::upper($ui), + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'user-interface' => Str::upper($ui), 'base-controller' => $basecontroller, - 'model' => $model, - 'models' => $models, - 'entity' => $entity, + 'model' => $model, + 'models' => $models, + 'entity' => $entity, 'entities' => $entities, ], 'file-parameters' => [ @@ -99,6 +107,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return 'Controller'; diff --git a/src/Generator/Commands/EventGenerator.php b/src/Generator/Commands/EventGenerator.php index 6deff42d6..6a21791da 100644 --- a/src/Generator/Commands/EventGenerator.php +++ b/src/Generator/Commands/EventGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('model', 'Enter the name of the Model to generate this Event for', Str::ucfirst($this->containerName)); $listener = $this->option('listener'); - if (is_null($listener)) { + + if (\is_null($listener)) { $listener = $this->checkParameterOrConfirm('listener', 'Do you want to generate a Listener for this Event?', false); + if ($listener) { $this->call('apiato:make:listener', [ - '--section' => $this->sectionName, + '--section' => $this->sectionName, '--container' => $this->containerName, - '--file' => $this->fileName . 'Listener', - '--event' => $this->fileName, + '--file' => $this->fileName . 'Listener', + '--event' => $this->fileName, ]); } } @@ -69,17 +79,17 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - '_model' => Str::lower($model), + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + '_model' => Str::lower($model), ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/EventListenerGenerator.php b/src/Generator/Commands/EventListenerGenerator.php index 5c5b426bc..632214fbb 100644 --- a/src/Generator/Commands/EventListenerGenerator.php +++ b/src/Generator/Commands/EventListenerGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('event', 'Enter the name of the Event to generate this Listener for'); return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $event, + 'class-name' => $this->fileName, + 'model' => $event, ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/ExceptionGenerator.php b/src/Generator/Commands/ExceptionGenerator.php index 4d43d61e4..579d60e3b 100644 --- a/src/Generator/Commands/ExceptionGenerator.php +++ b/src/Generator/Commands/ExceptionGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/FunctionalTestGenerator.php b/src/Generator/Commands/FunctionalTestGenerator.php index a725c7df6..4dd650df8 100644 --- a/src/Generator/Commands/FunctionalTestGenerator.php +++ b/src/Generator/Commands/FunctionalTestGenerator.php @@ -1,5 +1,7 @@ checkParameterOrChoice('ui', 'Select the UI for the Test', ['API', 'CLI'], 0)); @@ -56,10 +64,11 @@ public function getUserInputs(): array|null $stub = $this->option('stub'); $url = $this->option('url'); - if ('api' === $ui) { + if ($ui === 'api') { $this->pathStructure = '{section-name}/{container-name}/Tests/Functional/API/*'; } - if ('cli' === $ui) { + + if ($ui === 'cli') { $this->pathStructure = '{section-name}/{container-name}/Tests/Functional/CLI/*'; } @@ -70,21 +79,21 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, 'user-interface' => Str::upper($ui), ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - '_model' => Str::camel($model), - 'models' => $models, - '_models' => Str::lower($models), - 'url' => $url, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + '_model' => Str::camel($model), + 'models' => $models, + '_models' => Str::lower($models), + 'url' => $url, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -92,6 +101,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return 'DefaultFunctionalTest'; diff --git a/src/Generator/Commands/JobGenerator.php b/src/Generator/Commands/JobGenerator.php index c1df45bcf..5bdb21706 100644 --- a/src/Generator/Commands/JobGenerator.php +++ b/src/Generator/Commands/JobGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -66,6 +74,7 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return 'DefaultJob'; diff --git a/src/Generator/Commands/MailGenerator.php b/src/Generator/Commands/MailGenerator.php index 34b79e54f..22090fcf8 100644 --- a/src/Generator/Commands/MailGenerator.php +++ b/src/Generator/Commands/MailGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('view', 'Enter the name of the view to be loaded when sending this Mail', ''); $subject = $this->checkParameterOrAsk('subject', "What's the the subject this Mail?", ''); return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, - 'sectionName' => Str::camel($this->sectionName), + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, + 'sectionName' => Str::camel($this->sectionName), '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'containerName' => Str::camel($this->containerName), - 'class-name' => $this->fileName, - 'view' => $view, - 'subject' => $subject, + 'container-name' => $this->containerName, + 'containerName' => Str::camel($this->containerName), + 'class-name' => $this->fileName, + 'view' => $view, + 'subject' => $subject, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -73,6 +81,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return 'DefaultMail'; diff --git a/src/Generator/Commands/MiddlewareGenerator.php b/src/Generator/Commands/MiddlewareGenerator.php index 6c640febb..db147e3fe 100644 --- a/src/Generator/Commands/MiddlewareGenerator.php +++ b/src/Generator/Commands/MiddlewareGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -63,6 +71,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return 'DefaultMiddleware'; diff --git a/src/Generator/Commands/MigrationGenerator.php b/src/Generator/Commands/MigrationGenerator.php index 0a5c07daf..cacf9f4ca 100644 --- a/src/Generator/Commands/MigrationGenerator.php +++ b/src/Generator/Commands/MigrationGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('tablename', 'Enter the name of the database table', Str::snake(Pluralizer::plural($this->containerName)))); @@ -57,7 +70,7 @@ public function getUserInputs(): array|null $exists = false; $folder = $this->parsePathStructure($this->pathStructure, [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ]); $folder = $this->getFilePath($folder); @@ -80,19 +93,19 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => Str::studly($this->fileName), - 'table-name' => $tableName, + 'container-name' => $this->containerName, + 'class-name' => Str::studly($this->fileName), + 'table-name' => $tableName, ], 'file-parameters' => [ - 'date' => Carbon::now()->format('Y_m_d_His'), + 'date' => Carbon::now()->format(self::FORMAT_TIME), 'file-name' => $this->fileName, ], ]; @@ -101,6 +114,7 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return 'create_' . Str::snake(Pluralizer::plural($this->containerName)) . '_table'; @@ -109,6 +123,7 @@ public function getDefaultFileName(): string /** * Removes "special characters" from a string. */ + #[\Override] protected function removeSpecialChars(string $str): string { return $str; diff --git a/src/Generator/Commands/ModelFactoryGenerator.php b/src/Generator/Commands/ModelFactoryGenerator.php index 9facd6502..098f8cc06 100644 --- a/src/Generator/Commands/ModelFactoryGenerator.php +++ b/src/Generator/Commands/ModelFactoryGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('model', 'Enter the name of the Model to generate this Factory for'); return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - '_model' => Str::lower($model), + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + '_model' => Str::lower($model), ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/ModelGenerator.php b/src/Generator/Commands/ModelGenerator.php index 0cc1930f6..0e0453beb 100644 --- a/src/Generator/Commands/ModelGenerator.php +++ b/src/Generator/Commands/ModelGenerator.php @@ -1,5 +1,7 @@ checkParameterOrConfirm('repository', 'Do you want to generate the corresponding Repository for this Model?', true); + if ($repository) { // We need to generate a corresponding repository // so call the other command $status = $this->call('apiato:make:repository', [ - '--section' => $this->sectionName, + '--section' => $this->sectionName, '--container' => $this->containerName, - '--file' => $this->fileName . 'Repository', - '--model' => $this->fileName, + '--file' => $this->fileName . 'Repository', + '--model' => $this->fileName, ]); - if (0 !== $status) { + if ($status !== 0) { $this->printErrorMessage('Could not generate the corresponding Repository!'); } } return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'resource-key' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'resource-key' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/NotificationGenerator.php b/src/Generator/Commands/NotificationGenerator.php index 0f573cc9a..b14e7fd62 100644 --- a/src/Generator/Commands/NotificationGenerator.php +++ b/src/Generator/Commands/NotificationGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/PolicyGenerator.php b/src/Generator/Commands/PolicyGenerator.php index 29c53478e..c96ec3453 100644 --- a/src/Generator/Commands/PolicyGenerator.php +++ b/src/Generator/Commands/PolicyGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -63,6 +71,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return $this->containerName . 'Policy'; diff --git a/src/Generator/Commands/ReadmeGenerator.php b/src/Generator/Commands/ReadmeGenerator.php index bace26370..37dc9c73e 100644 --- a/src/Generator/Commands/ReadmeGenerator.php +++ b/src/Generator/Commands/ReadmeGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -66,11 +74,13 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return 'README'; } + #[\Override] public function getDefaultFileExtension(): string { return 'md'; diff --git a/src/Generator/Commands/RepositoryGenerator.php b/src/Generator/Commands/RepositoryGenerator.php index 7a0d8e518..aa064bfcf 100644 --- a/src/Generator/Commands/RepositoryGenerator.php +++ b/src/Generator/Commands/RepositoryGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('model', 'Enter the name of the Model to generate this Repository for'); return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - '_model' => Str::lower($model), + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + '_model' => Str::lower($model), ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/RequestGenerator.php b/src/Generator/Commands/RequestGenerator.php index 9f3d7b880..71acdf34c 100644 --- a/src/Generator/Commands/RequestGenerator.php +++ b/src/Generator/Commands/RequestGenerator.php @@ -1,5 +1,7 @@ checkParameterOrChoice('ui', 'Select the UI for the controller', ['API', 'WEB'], 0)); $stub = $this->option('stub'); @@ -62,17 +64,17 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, 'user-interface' => Str::upper($ui), ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'user-interface' => Str::upper($ui), + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'user-interface' => Str::upper($ui), ], 'file-parameters' => [ 'file-name' => $this->fileName, diff --git a/src/Generator/Commands/RouteGenerator.php b/src/Generator/Commands/RouteGenerator.php index d69272744..c30c3178f 100644 --- a/src/Generator/Commands/RouteGenerator.php +++ b/src/Generator/Commands/RouteGenerator.php @@ -1,5 +1,7 @@ checkParameterOrChoice('ui', 'Select the UI for the controller', ['API', 'WEB'], 0)); - $version = $this->checkParameterOrAsk('docversion', 'Enter the endpoint version (integer)', 1); + $version = $this->checkParameterOrAsk('docversion', 'Enter the endpoint version (integer)', '1'); $doctype = $this->checkParameterOrChoice('doctype', 'Select the type for this endpoint', ['private', 'public'], 0); $verb = Str::upper($this->checkParameterOrChoice('verb', 'Enter the HTTP verb of this endpoint (GET, POST,...)', ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], 0)); // Get the URI and remove the first trailing slash @@ -66,46 +76,54 @@ public function getUserInputs(): array|null $operation = '__invoke'; $sac = $this->checkParameterOrConfirm('sac', 'Is this a Single Action Controller route?', true); - if (!$sac) { + + if ($sac === false || $sac === null) { $operation = $this->checkParameterOrAsk('operation', 'Enter the name of the controller action', '__invoke'); } - $docUrl = \Safe\preg_replace('~{(.+?)}~', ':$1', $url); + $routeName = $operation; + + if ($operation === '__invoke') { + $routeName = $this->fileName; + } + + $docUrl = preg_replace('~{(.+?)}~', ':$1', $url); - $routeName = Str::lower($ui . '_' . $this->containerName . '_' . Str::snake($operation)); + $routeName = Str::lower($ui . '_' . $this->containerName . '_' . Str::snake($routeName)); $this->stubName = 'routes/' . $ui . '.mac.stub'; + if ($sac) { $this->stubName = 'routes/' . $ui . '.sac.stub'; } return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, 'user-interface' => Str::upper($ui), ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, - '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'operation' => $operation, - 'doc-api-name' => Str::studly($this->option('operation')), - 'user-interface' => Str::upper($ui), - 'endpoint-url' => $url, - 'endpoint-title' => Str::headline($operation), + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, + '_container-name' => Str::lower($this->containerName), + 'container-name' => $this->containerName, + 'operation' => $operation, + 'doc-api-name' => Str::studly($this->option('operation')), + 'user-interface' => Str::upper($ui), + 'endpoint-url' => $url, + 'endpoint-title' => Str::headline($operation), 'doc-endpoint-url' => '/v' . $version . '/' . $docUrl, 'endpoint-version' => $version, - 'http-verb' => Str::lower($verb), - 'doc-http-verb' => Str::upper($verb), - 'route-name' => $routeName, - 'auth-middleware' => Str::lower($ui), - 'controller-name' => $controllerName, + 'http-verb' => Str::lower($verb), + 'doc-http-verb' => Str::upper($verb), + 'route-name' => $routeName, + 'auth-middleware' => Str::lower($ui), + 'controller-name' => $controllerName, ], 'file-parameters' => [ - 'endpoint-name' => $this->fileName, - 'endpoint-version' => 'v' . $version, + 'endpoint-name' => $this->fileName, + 'endpoint-version' => 'v' . $version, 'documentation-type' => $doctype, ], ]; diff --git a/src/Generator/Commands/SeederGenerator.php b/src/Generator/Commands/SeederGenerator.php index df97e2705..ab5445865 100644 --- a/src/Generator/Commands/SeederGenerator.php +++ b/src/Generator/Commands/SeederGenerator.php @@ -1,61 +1,77 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -66,8 +82,18 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { - return $this->containerName . 'Seeder'; + return \sprintf('Order_%s_%sSeeder', $this->getDate(), $this->containerName); + } + + private function getDate(): string + { + if ($this->fileParametersDate === null) { + $this->fileParametersDate = Carbon::now()->format(self::FORMAT_TIME); + } + + return $this->fileParametersDate; } } diff --git a/src/Generator/Commands/ServiceProviderGenerator.php b/src/Generator/Commands/ServiceProviderGenerator.php index e580eb6b6..fb44f853d 100644 --- a/src/Generator/Commands/ServiceProviderGenerator.php +++ b/src/Generator/Commands/ServiceProviderGenerator.php @@ -1,5 +1,7 @@ option('stub'); + if (!$stub) { $stub = $this->checkParameterOrChoice( 'stub', @@ -58,22 +67,23 @@ public function getUserInputs(): array|null $stub = match ($stub) { 'EventServiceProvider' => 'event-service-provider', - default => 'service-provider', + default => 'service-provider', }; } - $this->stubName = "providers/$stub.stub"; + + $this->stubName = \sprintf('providers/%s.stub', $stub); return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -84,6 +94,7 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return 'ServiceProvider'; diff --git a/src/Generator/Commands/SubActionGenerator.php b/src/Generator/Commands/SubActionGenerator.php index 05811f717..6fbd254a4 100644 --- a/src/Generator/Commands/SubActionGenerator.php +++ b/src/Generator/Commands/SubActionGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -66,8 +74,9 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { - return 'DefaultAction'; + return 'DefaultSubAction'; } } diff --git a/src/Generator/Commands/TaskGenerator.php b/src/Generator/Commands/TaskGenerator.php index def90dac7..2353191ff 100644 --- a/src/Generator/Commands/TaskGenerator.php +++ b/src/Generator/Commands/TaskGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('model', 'Enter the name of the model this task is for.', $this->containerName); $stub = Str::lower( @@ -68,20 +76,20 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - 'models' => $models, - '_model' => Str::lower($model), - 'model_' => Str::camel($model), - 'event' => $event, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + 'models' => $models, + '_model' => Str::lower($model), + 'model_' => Str::camel($model), + 'event' => $event, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -92,6 +100,7 @@ public function getUserInputs(): array|null /** * Get the default file name for this component to be generated. */ + #[\Override] public function getDefaultFileName(): string { return 'DefaultTask'; diff --git a/src/Generator/Commands/TestCaseGenerator.php b/src/Generator/Commands/TestCaseGenerator.php index 54727b563..8a55d456b 100644 --- a/src/Generator/Commands/TestCaseGenerator.php +++ b/src/Generator/Commands/TestCaseGenerator.php @@ -1,5 +1,7 @@ checkParameterOrChoice('type', 'Select the TestCase type', ['Container', 'Unit', 'Functional', 'E2E', 'API', 'CLI', 'WEB'], 0)); $this->stubName = 'tests/testcase/' . $type . '.stub'; - if ('e2e' === $type) { + + if ($type === 'e2e') { $this->fileName = Str::upper($type) . $this->fileName; } else { $this->fileName = Str::ucfirst($type) . $this->fileName; } - if ('api' === $type || 'cli' === $type) { + + if ($type === 'api' || $type === 'cli') { $this->pathStructure = '{section-name}/{container-name}/Tests/Functional/*'; } - if ('web' === $type) { + + if ($type === 'web') { $this->pathStructure = '{section-name}/{container-name}/Tests/E2E/*'; } return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -80,6 +91,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return 'TestCase'; diff --git a/src/Generator/Commands/TransformerGenerator.php b/src/Generator/Commands/TransformerGenerator.php index 36b55d3ae..c3d84206c 100644 --- a/src/Generator/Commands/TransformerGenerator.php +++ b/src/Generator/Commands/TransformerGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('model', 'Enter the name of the Model to generate this Transformer for'); - $full = $this->checkParameterOrConfirm('full', 'Generate a Transformer with all fields', false); + $full = (bool)$this->checkParameterOrConfirm('full', 'Generate a Transformer with all fields', false); $attributes = $this->getListOfAllAttributes($full, $model); return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - '_model' => Str::lower($model), - 'attributes' => $attributes, + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + '_model' => Str::lower($model), + 'attributes' => $attributes, ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -75,7 +83,7 @@ public function getUserInputs(): array|null ]; } - private function getListOfAllAttributes(string|bool|array|null $full, string $model): string + private function getListOfAllAttributes(bool $full, string $model): string { $indent = str_repeat(' ', 12); $_model = Str::lower($model); @@ -89,7 +97,7 @@ private function getListOfAllAttributes(string|bool|array|null $full, string $mo $columns = Schema::getColumnListing($obj->getTable()); foreach ($columns as $column) { - if (in_array($column, $obj->getHidden(), false)) { + if (\in_array($column, $obj->getHidden(), false)) { // Skip all hidden fields of respective model continue; } @@ -102,9 +110,12 @@ private function getListOfAllAttributes(string|bool|array|null $full, string $mo 'id' => '$' . $_model . '->getHashedKey()', ]); + $lengths = array_map(Str::length(...), array_keys($fields)); + $maxLength = max($lengths); $attributes = ''; foreach ($fields as $key => $value) { - $attributes .= $indent . "'$key' => $value," . $this->getEndOfLine($key, $fields); + $tab = str_repeat(' ', $maxLength - Str::length($key)); + $attributes .= $indent . \sprintf("'%s'%s => %s,", $key, $tab, $value) . $this->getEndOfLine($key, $fields); } return $attributes; @@ -115,6 +126,6 @@ private function getEndOfLine(string $currentKey, array $fields): string $keys = array_keys($fields); $lastKey = end($keys); - return $currentKey === $lastKey ? '' : PHP_EOL; + return $currentKey === (string)$lastKey ? '' : PHP_EOL; } } diff --git a/src/Generator/Commands/UnitTestGenerator.php b/src/Generator/Commands/UnitTestGenerator.php index 90c3bcfeb..db41bba85 100644 --- a/src/Generator/Commands/UnitTestGenerator.php +++ b/src/Generator/Commands/UnitTestGenerator.php @@ -1,5 +1,7 @@ checkParameterOrAsk('foldername', 'Enter the folder name to create the test in'); - if ($folderName) { + + if ($folderName !== '') { $this->pathStructure = '{section-name}/{container-name}/Tests/Unit/' . $folderName . '/*'; } @@ -65,6 +74,7 @@ public function getUserInputs(): array|null if ($stub) { $stubBasePath = 'tests/unit'; + if ($stubFolderName) { $stubBasePath = 'tests/unit/' . $stubFolderName; } @@ -81,22 +91,22 @@ public function getUserInputs(): array|null return [ 'path-parameters' => [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'model' => $model, - '_model' => Str::camel($model), - 'models' => $models, - '_models' => Str::lower($models), - 'event' => $event, - 'table-name' => $tableName, - '_table-name_' => Str::studly($tableName), + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'model' => $model, + '_model' => Str::camel($model), + 'models' => $models, + '_models' => Str::lower($models), + 'event' => $event, + 'table-name' => $tableName, + '_table-name_' => Str::studly($tableName), ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -104,6 +114,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return 'DefaultUnitTest'; diff --git a/src/Generator/Commands/ValueGenerator.php b/src/Generator/Commands/ValueGenerator.php index face12479..e92c3acf4 100644 --- a/src/Generator/Commands/ValueGenerator.php +++ b/src/Generator/Commands/ValueGenerator.php @@ -1,5 +1,7 @@ [ - 'section-name' => $this->sectionName, + 'section-name' => $this->sectionName, 'container-name' => $this->containerName, ], 'stub-parameters' => [ - '_section-name' => Str::lower($this->sectionName), - 'section-name' => $this->sectionName, + '_section-name' => Str::lower($this->sectionName), + 'section-name' => $this->sectionName, '_container-name' => Str::lower($this->containerName), - 'container-name' => $this->containerName, - 'class-name' => $this->fileName, - 'resource-key' => strtolower(Pluralizer::plural($this->fileName)), + 'container-name' => $this->containerName, + 'class-name' => $this->fileName, + 'resource-key' => strtolower(Pluralizer::plural($this->fileName)), ], 'file-parameters' => [ 'file-name' => $this->fileName, @@ -65,6 +73,7 @@ public function getUserInputs(): array|null ]; } + #[\Override] public function getDefaultFileName(): string { return 'DefaultValue'; diff --git a/src/Generator/Generator.php b/src/Generator/Generator.php index 405088d08..032478634 100644 --- a/src/Generator/Generator.php +++ b/src/Generator/Generator.php @@ -1,10 +1,11 @@ sectionName = $this->removeSpecialChars($this->sectionName); $this->containerName = $this->removeSpecialChars($this->containerName); - if ('Configuration' !== $this->fileType) { + + if ($this->fileType !== 'Configuration') { $this->fileName = $this->removeSpecialChars($this->fileName); } @@ -117,10 +121,11 @@ public function handle(): int|null // Get user inputs $this->userData = $this->getUserInputs(); - if (null === $this->userData) { + if ($this->userData === null || $this->userData === []) { // The user skipped this step - return null; + return Command::SUCCESS; } + $this->userData = $this->sanitizeUserData($this->userData); // Get the actual path of the output file as well as the correct filename @@ -138,19 +143,23 @@ public function handle(): int|null } // Exit the command successfully - return 0; + return Command::SUCCESS; } /** * Checks if the param is set (via CLI), otherwise asks the user for a value. */ - protected function checkParameterOrAsk($param, $question, string|null $default = null): mixed - { + protected function checkParameterOrAsk( + string $param, + string $question, + null|string|int $default = null, + ): array|string|int { // Check if we already have a param set $value = $this->option($param); - if (null === $value) { - // There was no value provided via CLI, so ask the user… - $value = text( + + if ($value === null) { + // There was no value provided via CLI, so ask the user. + return text( label: $question, default: $default ?? '', ); @@ -173,31 +182,10 @@ protected function getDefaultFileName(): string protected function removeSpecialChars(string $str): string { // remove everything that is NOT a character or digit - return \Safe\preg_replace('/[^A-Za-z0-9]/', '', $str); + return preg_replace('/[^A-Za-z0-9]/', '', $str); } - /** - * Checks, if the data from the generator contains path, stub and file-parameters. - * Adds empty arrays, if they are missing. - */ - private function sanitizeUserData(array $data): mixed - { - if (!array_key_exists('path-parameters', $data)) { - $data['path-parameters'] = []; - } - - if (!array_key_exists('stub-parameters', $data)) { - $data['stub-parameters'] = []; - } - - if (!array_key_exists('file-parameters', $data)) { - $data['file-parameters'] = []; - } - - return $data; - } - - protected function getFilePath($path): string + protected function getFilePath(string $path): string { // Complete the missing parts of the path $path = base_path() . '/' . @@ -246,35 +234,58 @@ protected function getOptions(): array return array_merge($this->defaultInputs, $this->inputs); } - protected function getInput($arg, bool $trim = true): array|string|null + protected function getInput($arg, bool $trim = true): null|array|string { - return $trim ? $this->trimString($this->argument($arg)) : $this->argument($arg); + return $trim ? trim($this->argument($arg)) : $this->argument($arg); } /** * Checks if the param is set (via CLI), otherwise proposes choices to the user. */ - protected function checkParameterOrChoice($param, $question, array $choices, mixed $default = null): bool|array|string|null + protected function checkParameterOrChoice(string $param, string $question, array $choices, null|string|int $default = null): null|bool|array|string { // Check if we already have a param set $value = $this->option($param); - if (null === $value) { - // There was no value provided via CLI, so ask the user… - $value = select($question, $choices, $default); + + if ($value === null) { + // There was no value provided via CLI, so ask the user. + return select($question, $choices, $default); } return $value; } - protected function checkParameterOrConfirm($param, $question, bool $default = false): string|array|bool|null + protected function checkParameterOrConfirm(string $param, string $question, bool $default = false): null|string|array|bool { // Check if we already have a param set $value = $this->option($param); - if (null === $value) { - // There was no value provided via CLI, so ask the user... - $value = confirm($question, $default); + + if ($value === null) { + // There was no value provided via CLI, so ask the user. + return confirm($question, $default); } return $value; } + + /** + * Checks, if the data from the generator contains path, stub and file-parameters. + * Adds empty arrays, if they are missing. + */ + private function sanitizeUserData(array $data): array + { + if (!\array_key_exists('path-parameters', $data)) { + $data['path-parameters'] = []; + } + + if (!\array_key_exists('stub-parameters', $data)) { + $data['stub-parameters'] = []; + } + + if (!\array_key_exists('file-parameters', $data)) { + $data['file-parameters'] = []; + } + + return $data; + } } diff --git a/src/Generator/GeneratorsServiceProvider.php b/src/Generator/GeneratorsServiceProvider.php index f71b8ed9b..54a75da1f 100644 --- a/src/Generator/GeneratorsServiceProvider.php +++ b/src/Generator/GeneratorsServiceProvider.php @@ -1,5 +1,7 @@ update{{model}}Task->run($data, $request->id); + return $this->update{{model}}Task->run($request->id, $data); } } diff --git a/src/Generator/Stubs/config.stub b/src/Generator/Stubs/config.stub index e1045aac4..e3fe0b1d0 100644 --- a/src/Generator/Stubs/config.stub +++ b/src/Generator/Stubs/config.stub @@ -1,5 +1,7 @@ {{_model}} = ${{_model}}->withoutRelations(); } /** diff --git a/src/Generator/Stubs/events/delete.stub b/src/Generator/Stubs/events/delete.stub index 1d7e52c06..a644ec257 100644 --- a/src/Generator/Stubs/events/delete.stub +++ b/src/Generator/Stubs/events/delete.stub @@ -1,5 +1,7 @@ fake()->name(), + ]; + } + + public function name(): self + { + return $this->state(fn (array $attributes): array => [ + 'name' => fake()->name(), + ]); } } diff --git a/src/Generator/Stubs/job.stub b/src/Generator/Stubs/job.stub index 3abc6b424..cdec3d798 100644 --- a/src/Generator/Stubs/job.stub +++ b/src/Generator/Stubs/job.stub @@ -1,5 +1,7 @@ id(); $table->timestamps(); }); diff --git a/src/Generator/Stubs/model.stub b/src/Generator/Stubs/model.stub index eac36b75b..1da39b04a 100644 --- a/src/Generator/Stubs/model.stub +++ b/src/Generator/Stubs/model.stub @@ -1,5 +1,7 @@ [ + // OrderShipped::class => [ // SendShipmentNotification::class, // ], ]; diff --git a/src/Generator/Stubs/providers/service-provider.stub b/src/Generator/Stubs/providers/service-provider.stub index 28500bd0d..4355380d5 100644 --- a/src/Generator/Stubs/providers/service-provider.stub +++ b/src/Generator/Stubs/providers/service-provider.stub @@ -1,5 +1,7 @@ '=', ]; diff --git a/src/Generator/Stubs/requests/create.stub b/src/Generator/Stubs/requests/create.stub index ca49b46bf..c10c984ea 100644 --- a/src/Generator/Stubs/requests/create.stub +++ b/src/Generator/Stubs/requests/create.stub @@ -1,5 +1,7 @@ '', 'roles' => ''] * * @apiHeader {String} accept=application/json @@ -21,10 +29,6 @@ * // Insert the response of the request here... * } */ - -use App\Containers\{{section-name}}\{{container-name}}\UI\API\Controllers\{{controller-name}}; -use Illuminate\Support\Facades\Route; - Route::{{http-verb}}('{{endpoint-url}}', [{{controller-name}}::class, '{{operation}}']) + ->name('{{route-name}}') ->middleware(['auth:{{auth-middleware}}']); - diff --git a/src/Generator/Stubs/routes/api.sac.stub b/src/Generator/Stubs/routes/api.sac.stub index 4dbea61fc..6ca538ca1 100644 --- a/src/Generator/Stubs/routes/api.sac.stub +++ b/src/Generator/Stubs/routes/api.sac.stub @@ -1,13 +1,21 @@ '', 'roles' => ''] * * @apiHeader {String} accept=application/json @@ -21,10 +29,6 @@ * // Insert the response of the request here... * } */ - -use App\Containers\{{section-name}}\{{container-name}}\UI\API\Controllers\{{controller-name}}; -use Illuminate\Support\Facades\Route; - Route::{{http-verb}}('{{endpoint-url}}', {{controller-name}}::class) + ->name('{{route-name}}') ->middleware(['auth:{{auth-middleware}}']); - diff --git a/src/Generator/Stubs/routes/generic.stub b/src/Generator/Stubs/routes/generic.stub index 1493d899d..399dcb57c 100644 --- a/src/Generator/Stubs/routes/generic.stub +++ b/src/Generator/Stubs/routes/generic.stub @@ -1,8 +1,10 @@ name('{{route-name}}') ->middleware(['auth:{{auth-middleware}}']); - diff --git a/src/Generator/Stubs/routes/web.mac.stub b/src/Generator/Stubs/routes/web.mac.stub index 1c17e258e..fff5c317c 100644 --- a/src/Generator/Stubs/routes/web.mac.stub +++ b/src/Generator/Stubs/routes/web.mac.stub @@ -1,8 +1,10 @@ name('{{route-name}}') ->middleware(['auth:{{auth-middleware}}']); - diff --git a/src/Generator/Stubs/routes/web.sac.stub b/src/Generator/Stubs/routes/web.sac.stub index d6ca3aa38..c24fd0836 100644 --- a/src/Generator/Stubs/routes/web.sac.stub +++ b/src/Generator/Stubs/routes/web.sac.stub @@ -1,8 +1,10 @@ name('{{route-name}}') ->middleware(['auth:{{auth-middleware}}']); - diff --git a/src/Generator/Stubs/seeder.stub b/src/Generator/Stubs/seeder.stub index ad074470e..ae471a1c4 100644 --- a/src/Generator/Stubs/seeder.stub +++ b/src/Generator/Stubs/seeder.stub @@ -1,5 +1,7 @@ repository->delete($id); } diff --git a/src/Generator/Stubs/tasks/find.stub b/src/Generator/Stubs/tasks/find.stub index 478257c96..acfa94e22 100644 --- a/src/Generator/Stubs/tasks/find.stub +++ b/src/Generator/Stubs/tasks/find.stub @@ -1,5 +1,7 @@ repository->findOrFail($id); } diff --git a/src/Generator/Stubs/tasks/generic.stub b/src/Generator/Stubs/tasks/generic.stub index df735b944..e7151b6f7 100644 --- a/src/Generator/Stubs/tasks/generic.stub +++ b/src/Generator/Stubs/tasks/generic.stub @@ -1,5 +1,7 @@ repository->addRequestCriteria()->paginate(); } diff --git a/src/Generator/Stubs/tasks/update.stub b/src/Generator/Stubs/tasks/update.stub index 05ec993b8..00119bdbb 100644 --- a/src/Generator/Stubs/tasks/update.stub +++ b/src/Generator/Stubs/tasks/update.stub @@ -1,5 +1,7 @@ repository->update($data, $id); } diff --git a/src/Generator/Stubs/tasks/with_event/create.stub b/src/Generator/Stubs/tasks/with_event/create.stub index 29801bcca..2e5ef137c 100644 --- a/src/Generator/Stubs/tasks/with_event/create.stub +++ b/src/Generator/Stubs/tasks/with_event/create.stub @@ -1,5 +1,7 @@ repository->delete($id); diff --git a/src/Generator/Stubs/tasks/with_event/find.stub b/src/Generator/Stubs/tasks/with_event/find.stub index 82b2bec65..eeae479b1 100644 --- a/src/Generator/Stubs/tasks/with_event/find.stub +++ b/src/Generator/Stubs/tasks/with_event/find.stub @@ -1,5 +1,7 @@ repository->findOrFail($id); diff --git a/src/Generator/Stubs/tasks/with_event/generic.stub b/src/Generator/Stubs/tasks/with_event/generic.stub index df735b944..29dc9d367 100644 --- a/src/Generator/Stubs/tasks/with_event/generic.stub +++ b/src/Generator/Stubs/tasks/with_event/generic.stub @@ -1,5 +1,7 @@ repository->addRequestCriteria()->paginate(); diff --git a/src/Generator/Stubs/tasks/with_event/update.stub b/src/Generator/Stubs/tasks/with_event/update.stub index 5e3f5798d..143b60807 100644 --- a/src/Generator/Stubs/tasks/with_event/update.stub +++ b/src/Generator/Stubs/tasks/with_event/update.stub @@ -1,5 +1,7 @@ repository->update($data, $id); diff --git a/src/Generator/Stubs/tests/e2e/web.stub b/src/Generator/Stubs/tests/e2e/web.stub index 949536ded..597b176f1 100644 --- a/src/Generator/Stubs/tests/e2e/web.stub +++ b/src/Generator/Stubs/tests/e2e/web.stub @@ -1,11 +1,16 @@ actingAs(User::factory()->createOne()); $data = [ - // TODO: test // 'something' => 'value', ]; @@ -23,7 +27,7 @@ final class {{class-name}} extends ApiTestCase $response->assertCreated(); $response->assertJson( - fn (AssertableJson $json) => + static fn (AssertableJson $json): AssertableJson => $json->has('data') ->where('data.type', '{{model}}') // ->where('data.something', $data['something']) diff --git a/src/Generator/Stubs/tests/functional/delete.stub b/src/Generator/Stubs/tests/functional/delete.stub index fbdb828d1..07900a688 100644 --- a/src/Generator/Stubs/tests/functional/delete.stub +++ b/src/Generator/Stubs/tests/functional/delete.stub @@ -1,5 +1,7 @@ assertOk(); $response->assertJson( - fn (AssertableJson $json) => + static fn (AssertableJson $json): AssertableJson => $json->has('data') ->where('data.id', ${{_model}}->getHashedKey()) ->etc() diff --git a/src/Generator/Stubs/tests/functional/generic.stub b/src/Generator/Stubs/tests/functional/generic.stub index 754c0fc17..58b8cf37d 100644 --- a/src/Generator/Stubs/tests/functional/generic.stub +++ b/src/Generator/Stubs/tests/functional/generic.stub @@ -1,5 +1,7 @@ assertOk(); $response->assertJson( - static fn (AssertableJson $json) => + static fn (AssertableJson $json): AssertableJson => $json->has('data', 2) ->etc() ); diff --git a/src/Generator/Stubs/tests/functional/update.stub b/src/Generator/Stubs/tests/functional/update.stub index 122d81782..613aeeb9d 100644 --- a/src/Generator/Stubs/tests/functional/update.stub +++ b/src/Generator/Stubs/tests/functional/update.stub @@ -1,5 +1,7 @@ assertOk(); $response->assertJson( - fn (AssertableJson $json) => + static fn (AssertableJson $json): AssertableJson => $json->has('data') ->where('data.type', '{{model}}') ->where('data.id', ${{_model}}->getHashedKey()) diff --git a/src/Generator/Stubs/tests/testcase/api.stub b/src/Generator/Stubs/tests/testcase/api.stub index cdd2afb59..9103340e9 100644 --- a/src/Generator/Stubs/tests/testcase/api.stub +++ b/src/Generator/Stubs/tests/testcase/api.stub @@ -1,5 +1,7 @@ 'int8', + 'id' => 'int8', 'created_at' => 'timestamp', 'updated_at' => 'timestamp', ]; diff --git a/src/Generator/Stubs/tests/unit/factory.stub b/src/Generator/Stubs/tests/unit/factory.stub index b35664289..023ec3445 100644 --- a/src/Generator/Stubs/tests/unit/factory.stub +++ b/src/Generator/Stubs/tests/unit/factory.stub @@ -1,19 +1,24 @@ make(); - $this->assertInstanceOf({{model}}::class, ${{_model}}); + self::assertInstanceOf({{model}}::class, ${{_model}}); } } diff --git a/src/Generator/Stubs/tests/unit/generic.stub b/src/Generator/Stubs/tests/unit/generic.stub index bccacf3f3..b5a46c233 100644 --- a/src/Generator/Stubs/tests/unit/generic.stub +++ b/src/Generator/Stubs/tests/unit/generic.stub @@ -1,5 +1,7 @@ 'new_field_value', ]; - $updated{{model}} = app(Update{{model}}Task::class)->run($data, ${{_model}}->id); + $updated{{model}} = app(Update{{model}}Task::class)->run(${{_model}}->id, $data); - $this->assertEquals(${{_model}}->id, $updated{{model}}->id); - // $this->assertEquals($data['some_field'], $updated{{model}}->some_field); + self::assertEquals(${{_model}}->id, $updated{{model}}->id); + // self::assertEquals($data['some_field'], $updated{{model}}->some_field); } } diff --git a/src/Generator/Stubs/tests/unit/tasks/with_event/create.stub b/src/Generator/Stubs/tests/unit/tasks/with_event/create.stub index f74ca9879..6cde4e2b5 100644 --- a/src/Generator/Stubs/tests/unit/tasks/with_event/create.stub +++ b/src/Generator/Stubs/tests/unit/tasks/with_event/create.stub @@ -1,5 +1,7 @@ run(${{_model}}->id); - $this->assertEquals(1, $result); + self::assertEquals(1, $result); Event::assertDispatched({{event}}::class); } } diff --git a/src/Generator/Stubs/tests/unit/tasks/with_event/find.stub b/src/Generator/Stubs/tests/unit/tasks/with_event/find.stub index 8b76146a7..90e489995 100644 --- a/src/Generator/Stubs/tests/unit/tasks/with_event/find.stub +++ b/src/Generator/Stubs/tests/unit/tasks/with_event/find.stub @@ -1,5 +1,7 @@ run(${{_model}}->id); - $this->assertEquals(${{_model}}->id, $found{{model}}->id); + self::assertEquals(${{_model}}->id, $found{{model}}->id); Event::assertDispatched({{event}}::class); } } diff --git a/src/Generator/Stubs/tests/unit/tasks/with_event/list.stub b/src/Generator/Stubs/tests/unit/tasks/with_event/list.stub index 3970c4eab..4ca2fe242 100644 --- a/src/Generator/Stubs/tests/unit/tasks/with_event/list.stub +++ b/src/Generator/Stubs/tests/unit/tasks/with_event/list.stub @@ -1,5 +1,7 @@ run(); - $this->assertCount(3, $found{{models}}); - $this->assertInstanceOf(LengthAwarePaginator::class, $found{{models}}); + self::assertCount(3, $found{{models}}); + self::assertInstanceOf(LengthAwarePaginator::class, $found{{models}}); Event::assertDispatched({{event}}::class); } } diff --git a/src/Generator/Stubs/tests/unit/tasks/with_event/update.stub b/src/Generator/Stubs/tests/unit/tasks/with_event/update.stub index 8f03dd268..f1ad25f97 100644 --- a/src/Generator/Stubs/tests/unit/tasks/with_event/update.stub +++ b/src/Generator/Stubs/tests/unit/tasks/with_event/update.stub @@ -1,5 +1,7 @@ 'new_field_data', ]; - $updated{{model}} = app(Update{{model}}Task::class)->run($data, ${{_model}}->id); + $updated{{model}} = app(Update{{model}}Task::class)->run(${{_model}}->id, $data); - $this->assertEquals(${{_model}}->id, $updated{{model}}->id); - // $this->assertEquals($data['some_field'], $updated{{model}}->some_field); + self::assertEquals(${{_model}}->id, $updated{{model}}->id); + // self::assertEquals($data['some_field'], $updated{{model}}->some_field); Event::assertDispatched({{event}}::class); } } diff --git a/src/Generator/Stubs/transformer.stub b/src/Generator/Stubs/transformer.stub index b4a9cf43c..ccf23dd3e 100644 --- a/src/Generator/Stubs/transformer.stub +++ b/src/Generator/Stubs/transformer.stub @@ -1,5 +1,7 @@ ${{_model}}->created_at, - 'updated_at' => ${{_model}}->updated_at, + 'created_at' => ${{_model}}->created_at, + 'updated_at' => ${{_model}}->updated_at, 'readable_created_at' => ${{_model}}->created_at->diffForHumans(), 'readable_updated_at' => ${{_model}}->updated_at->diffForHumans(), ]; diff --git a/src/Generator/Stubs/value.stub b/src/Generator/Stubs/value.stub index 7f59de5e4..c4a610b3f 100644 --- a/src/Generator/Stubs/value.stub +++ b/src/Generator/Stubs/value.stub @@ -1,5 +1,7 @@ fileSystem->put($filePath, $stubContent); } @@ -17,25 +21,23 @@ public function createDirectory(string $path): void if ($this->alreadyExists($path)) { $this->printErrorMessage($this->fileType . ' already exists'); - // the file does exist - return but NOT exit + // The file does exist - return but NOT exit. return; } try { - if (!$this->fileSystem->isDirectory(dirname($path))) { - $this->fileSystem->makeDirectory(dirname($path), 0777, true, true); + if (!$this->fileSystem->isDirectory(\dirname($path))) { + $this->fileSystem->makeDirectory(\dirname($path), 0777, true, true); } - } catch (\Exception) { - $this->printErrorMessage('Could not create ' . $path); + } catch (Throwable) { + $this->printErrorMessage(\sprintf('Could not create %s', $path)); } } /** * Determine if the file already exists. - * - * @return bool */ - protected function alreadyExists($path) + protected function alreadyExists(string $path): bool { return $this->fileSystem->exists($path); } diff --git a/src/Generator/Traits/FormatterTrait.php b/src/Generator/Traits/FormatterTrait.php deleted file mode 100644 index 6aa89334a..000000000 --- a/src/Generator/Traits/FormatterTrait.php +++ /dev/null @@ -1,23 +0,0 @@ -capitalize($className); - } - - public function capitalize($word): string - { - return ucfirst((string) $word); - } - - protected function trimString($string): string - { - return trim((string) $string); - } -} diff --git a/src/Generator/Traits/ParserTrait.php b/src/Generator/Traits/ParserTrait.php index 13288852a..aa4d6b522 100644 --- a/src/Generator/Traits/ParserTrait.php +++ b/src/Generator/Traits/ParserTrait.php @@ -1,31 +1,47 @@ parsedFileName, $path); } /** - * replaces the variables in the file structure with defined values. + * Replaces the variables in the file structure with defined values. */ - public function parseFileStructure($filename, $data): string|array + public function parseFileStructure(string $filename, array $data): string { - return str_replace(array_map([$this, 'maskFileVariables'], array_keys($data)), array_values($data), $filename); + return str_replace( + array_map( + [$this, 'maskFileVariables'], + array_keys($data) + ), + array_values($data), + $filename, + ); } /** - * replaces the variables in the stub file with defined values. + * Replaces the variables in the stub file with defined values. */ - public function parseStubContent($stub, $data): string|array + public function parseStubContent(string $stub, array $data): string|array { return str_replace(array_map([$this, 'maskStubVariables'], array_keys($data)), array_values($data), $stub); } diff --git a/src/Generator/Traits/PrinterTrait.php b/src/Generator/Traits/PrinterTrait.php index a1531eccb..918b11cc5 100644 --- a/src/Generator/Traits/PrinterTrait.php +++ b/src/Generator/Traits/PrinterTrait.php @@ -1,5 +1,7 @@ printInfoMessage('> Generating (' . $fileName . ') in (' . $containerName . ') Container.'); } - public function printInfoMessage($message): void + public function printInfoMessage(string $message): void { info($message); } @@ -22,7 +24,7 @@ public function printFinishedMessage(string $type): void $this->printInfoMessage($type . ' generated successfully.'); } - public function printErrorMessage($message): void + public function printErrorMessage(string $message): void { error($message); } diff --git a/src/Generator/Traits/UIGeneratorTrait.php b/src/Generator/Traits/UIGeneratorTrait.php new file mode 100644 index 000000000..05751729c --- /dev/null +++ b/src/Generator/Traits/UIGeneratorTrait.php @@ -0,0 +1,66 @@ +sectionName; + $_sectionName = Str::lower($this->sectionName); + + $containerName = $this->containerName; + $_containerName = Str::lower($this->containerName); + + $model = $this->containerName; + $models = Pluralizer::plural($model); + + $this->printInfoMessage('Generating README File'); + $this->call('apiato:make:readme', [ + '--section' => $sectionName, + '--container' => $containerName, + '--file' => 'README', + ]); + + $this->printInfoMessage('Generating Configuration File'); + $this->call('apiato:make:configuration', [ + '--section' => $sectionName, + '--container' => $containerName, + '--file' => Str::camel($this->sectionName) . '-' . Str::camel($this->containerName), + ]); + + $this->printInfoMessage('Generating Model and Repository'); + $this->call('apiato:make:model', [ + '--section' => $sectionName, + '--container' => $containerName, + '--file' => $model, + '--repository' => true, + ]); + + $this->printInfoMessage('Generating a basic Migration file'); + $this->call('apiato:make:migration', [ + '--section' => $sectionName, + '--container' => $containerName, + '--file' => 'create_' . Str::snake($models) . '_table', + '--tablename' => Str::snake($models), + ]); + + return [ + $sectionName, + $_sectionName, + $containerName, + $_containerName, + $model, + $models, + ]; + } +} diff --git a/src/Http/Middleware/ProcessETag.php b/src/Http/Middleware/ProcessETag.php index 9caed5b2a..4038e1515 100644 --- a/src/Http/Middleware/ProcessETag.php +++ b/src/Http/Middleware/ProcessETag.php @@ -1,5 +1,7 @@ headers->set('Etag', $etag); if ($request->hasHeader('if-none-match') && $request->header('if-none-match') === $etag) { - $response->setStatusCode(304); + $response->setStatusCode(Response::HTTP_NOT_MODIFIED); } return $response; diff --git a/src/Http/Middleware/ValidateJsonContent.php b/src/Http/Middleware/ValidateJsonContent.php index a74d29977..2eb3a5684 100644 --- a/src/Http/Middleware/ValidateJsonContent.php +++ b/src/Http/Middleware/ValidateJsonContent.php @@ -1,5 +1,7 @@ request->has(config()->string('fractal.auto_includes.request_key')); + return $this->request->has(config()?->string('fractal.auto_includes.request_key')); } /** @@ -34,7 +35,7 @@ public function getValidRelationsFor(Model $model): array return $this->getDeepestRelations( ...array_filter( array_map( - static fn (string $include) => self::isValidRelationOf($model, ...explode('.', $include)), + static fn (string $include): ?string => self::isValidRelationOf($model, ...explode('.', $include)), $this->parseIncludes(), ), ), @@ -46,7 +47,7 @@ public function getValidRelationsFor(Model $model): array */ public function getDeepestRelations(string ...$relations): array { - return array_filter($relations, static function ($relation) use ($relations) { + return array_filter($relations, static function (string $relation) use ($relations): bool { foreach ($relations as $otherRelation) { if ($relation !== $otherRelation && Str::startsWith($otherRelation, $relation . '.')) { return false; @@ -57,9 +58,9 @@ public function getDeepestRelations(string ...$relations): array }); } - public static function isValidRelationOf(Model $model, string ...$relationParts): string|null + public static function isValidRelationOf(Model $model, string ...$relationParts): null|string { - if ([] === $relationParts) { + if ($relationParts === []) { return null; } @@ -74,13 +75,13 @@ public static function isValidRelationOf(Model $model, string ...$relationParts) $nextModel = $relation->getRelated(); - if ([] === $relationParts) { + if ($relationParts === []) { return $relationName; } $nextRelation = self::isValidRelationOf($nextModel, ...$relationParts); - if (!is_null($nextRelation)) { + if (!\is_null($nextRelation)) { return $relationName . '.' . $nextRelation; } @@ -89,7 +90,7 @@ public static function isValidRelationOf(Model $model, string ...$relationParts) public static function figureOutRelationName(string $includeName): string { - return Str::of($includeName)->camel(); + return Str::of($includeName)->camel()->toString(); } /** @@ -107,7 +108,7 @@ public function parseIncludes(): array $includes = $this->request->input($requestKey, []); - if (is_array($includes)) { + if (\is_array($includes)) { Assert::allString($includes); } else { Assert::string($includes); diff --git a/src/Http/Resources/Collection.php b/src/Http/Resources/Collection.php index ad4bde00a..38c8d8122 100644 --- a/src/Http/Resources/Collection.php +++ b/src/Http/Resources/Collection.php @@ -1,25 +1,33 @@ resourceKey)) { + if (!\is_null($this->resourceKey)) { return $this->resourceKey; } $resource = $this->data; - if (is_array($resource)) { + if (\is_array($resource)) { $resource = reset($resource); } + if ($resource instanceof \IteratorAggregate) { $resource = $resource->getIterator(); } + if ($resource instanceof \Iterator) { $resource = $resource->current(); } diff --git a/src/Http/Resources/HasResourceKey.php b/src/Http/Resources/HasResourceKey.php index d6a75dd8f..fc4096410 100644 --- a/src/Http/Resources/HasResourceKey.php +++ b/src/Http/Resources/HasResourceKey.php @@ -1,5 +1,7 @@ resourceKey)) { + if (!\is_null($this->resourceKey)) { return $this->resourceKey; } diff --git a/src/Http/Resources/ResourceKeyAware.php b/src/Http/Resources/ResourceKeyAware.php index a44ea491c..537c75ffa 100644 --- a/src/Http/Resources/ResourceKeyAware.php +++ b/src/Http/Resources/ResourceKeyAware.php @@ -1,5 +1,7 @@ getResourceName(); - if (is_null($resourceName)) { - return $this->getResource()->getResourceKey(); - } - - return $resourceName; - } - - /** - * Add meta information about available includes to the response. - */ - private function setAvailableIncludesMeta(): void - { - $this->addMeta([ - 'include' => $this->getTransformerAvailableIncludes(), - ]); - } - - /** - * Get the available includes of the transformer. - * - * @return string[] - */ - private function getTransformerAvailableIncludes(): array - { - if (is_null($this->transformer) || is_callable($this->transformer)) { - return []; - } - - $includes = null; - - if (is_string($this->transformer)) { - Assert::subclassOf($this->transformer, TransformerAbstract::class); - - $includes = (new $this->transformer())->getAvailableIncludes(); - } - - if ($this->transformer instanceof TransformerAbstract) { - $includes = $this->transformer->getAvailableIncludes(); - } - - Assert::allString($includes); - - return $includes; - } - /** * Get the resource class based on the data type. */ @@ -100,11 +53,11 @@ public function getResourceClass(): string { $this->dataType = $this->determineDataType($this->data); - if ('item' === $this->dataType) { + if ($this->dataType === 'item') { return Item::class; } - if ('collection' === $this->dataType) { + if ($this->dataType === 'collection') { return Collection::class; } @@ -113,6 +66,9 @@ public function getResourceClass(): string /** * Convert the response data to an array. + * + * @throws InvalidTransformation + * @throws NoTransformerSpecified */ public function toArray(): array { @@ -124,9 +80,9 @@ public function toArray(): array * * @param array $headers */ - public function json(mixed $data = null, int $status = 200, array $headers = [], int $options = 0): JsonResponse + public function json(mixed $data = null, int $status = SymfonyResponse::HTTP_OK, array $headers = [], int $options = 0): JsonResponse { - if (is_null($data) && !is_null($this->data)) { + if (\is_null($data) && !\is_null($this->data)) { return $this->respond($status, $headers, $options); } @@ -140,11 +96,11 @@ public function json(mixed $data = null, int $status = 200, array $headers = [], */ public function accepted(mixed $data = null, array $headers = [], int $options = 0): JsonResponse { - if (is_null($this->getTransformer())) { + if (\is_null($this->getTransformer())) { $this->transformWith(Transformer::empty()); } - return $this->json($data, 202, $headers, $options); + return $this->json($data, SymfonyResponse::HTTP_ACCEPTED, $headers, $options); } /** @@ -154,11 +110,11 @@ public function accepted(mixed $data = null, array $headers = [], int $options = */ public function created(mixed $data = null, array $headers = [], int $options = 0): JsonResponse { - if (is_null($this->getTransformer())) { + if (\is_null($this->getTransformer())) { $this->transformWith(Transformer::empty()); } - return $this->json($data, 201, $headers, $options); + return $this->json($data, SymfonyResponse::HTTP_CREATED, $headers, $options); } /** @@ -168,11 +124,11 @@ public function created(mixed $data = null, array $headers = [], int $options = */ public function ok(mixed $data = null, array $headers = [], int $options = 0): JsonResponse { - if (is_null($this->getTransformer())) { + if (\is_null($this->getTransformer())) { $this->transformWith(Transformer::empty()); } - return $this->json($data, 200, $headers, $options); + return $this->json($data, SymfonyResponse::HTTP_OK, $headers, $options); } /** @@ -184,6 +140,60 @@ public function noContent(array $headers = [], int $options = 0): JsonResponse { $this->transformWith(Transformer::empty()); - return new JsonResponse(null, 204, $headers, $options); + return new JsonResponse(null, SymfonyResponse::HTTP_NO_CONTENT, $headers, $options); + } + + /** + * Get the resource key or a default value if none is set. + * + * @throws InvalidTransformation + */ + private function resourceKeyOrDefault(): string + { + $resourceName = $this->getResourceName(); + + if (\is_null($resourceName) || $resourceName === false) { + return $this->getResource()->getResourceKey(); + } + + return $resourceName; + } + + /** + * Add meta information about available includes to the response. + */ + private function setAvailableIncludesMeta(): void + { + $this->addMeta([ + 'include' => $this->getTransformerAvailableIncludes(), + ]); + } + + /** + * Get the available includes of the transformer. + * + * @return string[] + */ + private function getTransformerAvailableIncludes(): array + { + if (\is_null($this->transformer) || \is_callable($this->transformer)) { + return []; + } + + $includes = null; + + if (\is_string($this->transformer)) { + Assert::subclassOf($this->transformer, TransformerAbstract::class); + + $includes = (new $this->transformer())->getAvailableIncludes(); + } + + if ($this->transformer instanceof TransformerAbstract) { + $includes = $this->transformer->getAvailableIncludes(); + } + + Assert::allString($includes); + + return $includes; } } diff --git a/src/Linters/Rector/AssertInstanceToStaticCallRector.php b/src/Linters/Rector/AssertInstanceToStaticCallRector.php new file mode 100644 index 000000000..0f8ad434d --- /dev/null +++ b/src/Linters/Rector/AssertInstanceToStaticCallRector.php @@ -0,0 +1,131 @@ + + */ +final class AssertInstanceToStaticCallRector extends AbstractRector implements MinPhpVersionInterface +{ + /** + * @var string[] + */ + private const ASSERT_METHODS = [ + 'assertInstanceOf', + 'assertNotInstanceOf', + 'assertContains', + 'assertNotContains', + 'markTestSkipped', + 'assertFalse', + 'assertTrue', + 'assertSame', + 'assertEquals', + 'assertCount', + 'assertNull', + 'assertNotNull', + 'assertEmpty', + 'assertNotEmpty', + 'assertIsString', + 'assertIsArray', + 'assertArrayHasKey', + 'assertArrayNotHasKey', + 'assertDatabaseTable', + ]; + + public function __construct( + private readonly TestsNodeAnalyzer $testsNodeAnalyzer, + ) { + } + + /** + * @throws PoorDocumentationException + */ + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Changes PHPUnit assertion instance calls like $this->assertInstanceOf() to static calls like self::assertInstanceOf()', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; + +final class SomeTest extends TestCase +{ + public function test(): void + { + $this->assertInstanceOf(Something::class, $object); + $this->assertTrue($value); + $this->assertCount(5, $items); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; + +final class SomeTest extends TestCase +{ + public function test(): void + { + self::assertInstanceOf(Something::class, $object); + self::assertTrue($value); + self::assertCount(5, $items); + } +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (!$this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + if (!$this->isName($node->var, 'this')) { + return null; + } + + if (!$this->isNames($node->name, self::ASSERT_METHODS)) { + return null; + } + + return new StaticCall( + new Name('self'), + $this->getName($node->name), + $node->args + ); + } + + public function provideMinPhpVersion(): int + { + return PhpVersion::PHP_82; + } +} diff --git a/src/Linters/Rector/MockObjectStaticToInstanceCallRector.php b/src/Linters/Rector/MockObjectStaticToInstanceCallRector.php new file mode 100644 index 000000000..c080b626c --- /dev/null +++ b/src/Linters/Rector/MockObjectStaticToInstanceCallRector.php @@ -0,0 +1,119 @@ + + */ +final class MockObjectStaticToInstanceCallRector extends AbstractRector implements MinPhpVersionInterface +{ + /** + * @var array + */ + private const MOCK_METHODS = ['any', 'once', 'never', 'atLeast', 'atLeastOnce', 'atMost', 'exactly']; + + public function __construct( + private readonly TestsNodeAnalyzer $testsNodeAnalyzer, + ) { + } + + /** + * @throws PoorDocumentationException + */ + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Changes PHPUnit MockObject static calls like self::any() to instance calls like $this->any()', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; + +final class SomeTest extends TestCase +{ + public function test(): void + { + $mock = $this->createMock(Something::class); + $mock->expects(self::any()) + ->method('someMethod') + ->willReturn(true); + + $mock->expects(self::once()) + ->method('otherMethod'); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; + +final class SomeTest extends TestCase +{ + public function test(): void + { + $mock = $this->createMock(Something::class); + $mock->expects($this->any()) + ->method('someMethod') + ->willReturn(true); + + $mock->expects($this->once()) + ->method('otherMethod'); + } +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [StaticCall::class]; + } + + /** + * @param StaticCall $node + */ + public function refactor(Node $node): ?Node + { + if (!$this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + if (!$this->isNames($node->class, ['self', 'static'])) { + return null; + } + + if (!$this->isNames($node->name, self::MOCK_METHODS)) { + return null; + } + + return new MethodCall( + new Variable('this'), + $node->name, + $node->args + ); + } + + public function provideMinPhpVersion(): int + { + return PhpVersion::PHP_82; + } +} diff --git a/src/Macros/MacroServiceProvider.php b/src/Macros/MacroServiceProvider.php index 1b77b9358..73bf6373d 100644 --- a/src/Macros/MacroServiceProvider.php +++ b/src/Macros/MacroServiceProvider.php @@ -1,5 +1,7 @@ - /* @var Collection $this */ + fn (string $hashedValue, string $key = 'id'): bool => /** @var Collection $this */ $this->contains($key, hashids()->decode($hashedValue)), ); } @@ -34,6 +35,7 @@ public function boot(): void * or throws an exception if any value fails to decode. */ function (): Collection { + /** @var Collection $this */ return $this->map(static fn (string $id): int => hashids()->decodeOrFail($id)); }, ); @@ -43,9 +45,17 @@ function (): Collection { Config::macro( 'unset', function (array|string|int|float $key): void { - /* @var Repository $this */ - Arr::forget($this->items, $key); - }, + $deleter = \Closure::bind( + static function (Repository $repo, array|string|int|float $key): void { + Arr::forget($repo->items, $key); + }, + null, + Repository::class + ); + + /** @var Repository $this */ + $deleter($this, $key); + } ); } @@ -53,7 +63,7 @@ function (array|string|int|float $key): void { Request::macro( 'sanitize', function (array $fields): array { - /* @var Request $this */ + /** @var Request $this */ return Sanitizer::sanitize($this->all(), $fields); }, ); diff --git a/src/Support/DefaultProviders.php b/src/Support/DefaultProviders.php index dc536a88e..98f19ae14 100644 --- a/src/Support/DefaultProviders.php +++ b/src/Support/DefaultProviders.php @@ -1,5 +1,7 @@ manager->decode($hash); - if (1 === count($result) && is_int($result[0])) { + if (\count($result) === 1 && \is_int($result[0])) { return $result[0]; } - if (1 < count($result)) { + if (\count($result) > 1) { return $result; } @@ -50,7 +51,7 @@ public function decode(string $hash): int|array|null */ public function decodeOrFail(string ...$hash): int|array { - if (1 < count($hash)) { + if (\count($hash) > 1) { Assert::allStringNotEmpty($hash); return array_map(fn (string $id): int|array => $this->decodeOrFail($id), $hash); @@ -60,18 +61,18 @@ public function decodeOrFail(string ...$hash): int|array $result = $this->decode($hash[0]); - if (is_null($result)) { + if (\is_null($result)) { throw new \InvalidArgumentException('Invalid hash id.'); } return $result; } - public function encode(string|int ...$numbers): string|null + public function encode(string|int ...$numbers): null|string { $result = $this->manager->encode(...$numbers); - if ('' === $result) { + if ($result === '') { return null; } @@ -85,7 +86,7 @@ public function encodeOrFail(mixed ...$numbers): string { $result = $this->encode(...$numbers); - if (is_null($result)) { + if (\is_null($result)) { throw new \InvalidArgumentException('Encoding failed.'); } @@ -96,7 +97,7 @@ public function encodeOrFail(mixed ...$numbers): string * Dynamically pass method calls to the underlying resource. * * @param string $method - * @param array $parameters + * @param array $parameters */ public function __call($method, $parameters) { diff --git a/src/Support/Sanitizer.php b/src/Support/Sanitizer.php index 6d03b857d..4262ee7e4 100644 --- a/src/Support/Sanitizer.php +++ b/src/Support/Sanitizer.php @@ -1,5 +1,7 @@ $value) { - if (is_int($key)) { + if (\is_int($key)) { if (Arr::has($source, $value)) { Arr::set($sanitized, $value, Arr::get($source, $value)); } diff --git a/tests/Arch/CoreTest.php b/tests/Arch/CoreTest.php index d06e381d2..f3d83d85d 100644 --- a/tests/Arch/CoreTest.php +++ b/tests/Arch/CoreTest.php @@ -1,7 +1,10 @@ preset()->php(); @@ -18,7 +21,7 @@ 'Apiato\Core', 'Apiato\Generator', 'Apiato\Support\Facades', - Apiato\Http\Response::class, + Response::class, ]); arch('src/abstract') diff --git a/tests/Functional/Console/CommandServiceProviderTest.php b/tests/Functional/Console/CommandServiceProviderTest.php index 7ca86b3a2..276da88e5 100644 --- a/tests/Functional/Console/CommandServiceProviderTest.php +++ b/tests/Functional/Console/CommandServiceProviderTest.php @@ -1,5 +1,7 @@ true]); - $user = User::factory() + $model = User::factory() ->has(User::factory(1, ['name' => 'child_name']) ->has(Book::factory(2)), 'children') ->createOne(); $response = $this->getJson( - "v1/authors/{$user->getHashedKey()}/children/{$user->children->first()->name}/books/{$user->children->first()->books->last()->getHashedKey()}", + sprintf('v1/authors/%s/children/%s/books/%s', $model->getHashedKey(), $model->children->first()->name, $model->children->first()->books->last()->getHashedKey()), ); - expect($response->content())->toBe($user->children->first()->books->last()->author->name); + expect($response->content())->toBe($model->children->first()->books->last()->author->name); }, ); }); diff --git a/tests/Functional/Core/Repositories/RepositoryTest.php b/tests/Functional/Core/Repositories/RepositoryTest.php index 4bd3f648c..2cdccc6af 100644 --- a/tests/Functional/Core/Repositories/RepositoryTest.php +++ b/tests/Functional/Core/Repositories/RepositoryTest.php @@ -1,5 +1,7 @@ has(Book::factory(3)) ->createOne(); - $repository = new class extends UserRepository { + $repository = new class () extends UserRepository { public function shouldEagerLoadIncludes(): bool { return true; @@ -33,16 +35,18 @@ public function shouldEagerLoadIncludes(): bool expect($result)->toBeInstanceOf(Collection::class) ->each(function (Expectation $expectation) use ($userMustLoadRelations, $booksMustLoadRelations, $mustNotLoadRelations): void { - foreach ($userMustLoadRelations as $relation) { - $expectation->relationLoaded($relation)->toBeTrue(); + foreach ($userMustLoadRelations as $userMustLoadRelation) { + $expectation->relationLoaded($userMustLoadRelation)->toBeTrue(); } - foreach ($booksMustLoadRelations as $relation) { - $expectation->books->each(function (Expectation $expectation) use ($relation): void { - $expectation->relationLoaded($relation)->toBeTrue(); + + foreach ($booksMustLoadRelations as $bookMustLoadRelation) { + $expectation->books->each(function (Expectation $expectation) use ($bookMustLoadRelation): void { + $expectation->relationLoaded($bookMustLoadRelation)->toBeTrue(); }); } - foreach ($mustNotLoadRelations as $relation) { - $expectation->relationLoaded($relation)->toBeFalse(); + + foreach ($mustNotLoadRelations as $mustNotLoadRelation) { + $expectation->relationLoaded($mustNotLoadRelation)->toBeFalse(); } }); })->with([ @@ -87,10 +91,9 @@ public function shouldEagerLoadIncludes(): bool it('can disable eager loading', function (bool $enabled): void { request()->merge(['include' => 'books']); User::factory()->has(Book::factory())->createOne(); - $repository = new class($enabled) extends UserRepository { - public function __construct( - private readonly bool $enabled, - ) { + $repository = new class ($enabled) extends UserRepository { + public function __construct(private readonly bool $enabled) + { parent::__construct(); } @@ -111,7 +114,7 @@ public function shouldEagerLoadIncludes(): bool }); }); })->with([ - 'enabled' => [true], + 'enabled' => [true], 'disabled' => [false], ]); })->covers(Repository::class); diff --git a/tests/Functional/Core/Requests/RequestTest.php b/tests/Functional/Core/Requests/RequestTest.php index bc90e50aa..c2ee0295f 100644 --- a/tests/Functional/Core/Requests/RequestTest.php +++ b/tests/Functional/Core/Requests/RequestTest.php @@ -1,6 +1,9 @@ encodeOrFail($ids[0]), hashids()->encodeOrFail($ids[1]), ]; - $result = $this->patchJson("v1/books/{$bookIdHashed}", [ - 'title' => 'New Title', + $result = $this->patchJson('v1/books/' . $bookIdHashed, [ + 'title' => 'New Title', 'hashed_id' => $HashedId, 'author_id' => $authorIdHashed, - 'authors' => [ + 'authors' => [ [ - 'id' => $authorIdHashed, + 'id' => $authorIdHashed, 'name' => 'Author Name', ], ], - 'ids' => $hashedIds, + 'ids' => $hashedIds, 'just_a_number' => 123, - 'nested' => [ - 'id' => $nestedIdHashed, + 'nested' => [ + 'id' => $nestedIdHashed, 'ids' => $nestedIdsHashed, ], ]); @@ -48,22 +51,22 @@ expect($result->json()) ->toBe([ 'input(val)' => [ - 'id' => null, - 'id-default' => 100, - 'title' => 'New Title', - 'hashed_id' => $HashedId, - 'nested.id' => $expectedNestedId, + 'id' => null, + 'id-default' => 100, + 'title' => 'New Title', + 'hashed_id' => $HashedId, + 'nested.id' => $expectedNestedId, 'nested.with-default' => 200, - 'author_id' => $expectedAuthorId, - 'authors' => [ + 'author_id' => $expectedAuthorId, + 'authors' => [ ['id' => $expectedAuthorId, 'name' => 'Author Name'], ], - 'authors.*.id' => [$expectedAuthorId], + 'authors.*.id' => [$expectedAuthorId], 'authors.*.with-default' => [null], - 'ids' => $expectedIds, - 'with-default' => [1, 2, 3], - 'none_existing' => null, - 'optional_id' => null, + 'ids' => $expectedIds, + 'with-default' => [1, 2, 3], + 'none_existing' => null, + 'optional_id' => null, ], 'all(val)' => [ 'id' => [ @@ -93,56 +96,56 @@ ], ], 'route(val)' => [ - 'id' => $expectedBookId, + 'id' => $expectedBookId, 'none_existing' => null, ], 'request->val' => [ - 'id' => $expectedBookId, - 'title' => 'New Title', + 'id' => $expectedBookId, + 'title' => 'New Title', 'none_existing' => null, - 'optional_id' => null, + 'optional_id' => null, ], 'input()' => [ - 'title' => 'New Title', + 'title' => 'New Title', 'hashed_id' => $HashedId, 'author_id' => $expectedAuthorId, - 'authors' => [ + 'authors' => [ ['id' => $expectedAuthorId, 'name' => 'Author Name'], ], - 'ids' => $expectedIds, + 'ids' => $expectedIds, 'just_a_number' => 123, - 'nested' => [ - 'id' => $expectedNestedId, + 'nested' => [ + 'id' => $expectedNestedId, 'ids' => $expectedNestedIds, ], ], 'all()' => [ - 'title' => 'New Title', + 'title' => 'New Title', 'hashed_id' => $HashedId, 'author_id' => $expectedAuthorId, - 'authors' => [ + 'authors' => [ ['id' => $expectedAuthorId, 'name' => 'Author Name'], ], - 'ids' => $expectedIds, + 'ids' => $expectedIds, 'just_a_number' => 123, - 'nested' => [ - 'id' => $expectedNestedId, + 'nested' => [ + 'id' => $expectedNestedId, 'ids' => $expectedNestedIds, ], ], 'validated' => [ - 'title' => 'New Title', + 'title' => 'New Title', 'author_id' => $expectedAuthorId, - 'nested' => [ - 'id' => $expectedNestedId, + 'nested' => [ + 'id' => $expectedNestedId, 'ids' => $expectedNestedIds, ], - 'ids' => $expectedIds, + 'ids' => $expectedIds, 'authors' => [ ['id' => $expectedAuthorId], ], ], - 'route()::class' => Illuminate\Routing\Route::class, + 'route()::class' => Route::class, ]); })->with([ [true], diff --git a/tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php b/tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php index 2c63fc41a..8cb9ab195 100644 --- a/tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php +++ b/tests/Functional/Foundation/Configuration/ApplicationBuilderTest.php @@ -1,5 +1,7 @@ localization(); + $localization = apiato()->localization(); $this->app->setLocale('en'); - foreach ($configuration->paths() as $path) { - expect(__($configuration->buildNamespaceFor($path) . '::errors.forbidden'))->toBe('forbidden'); + foreach ($localization->paths() as $path) { + expect(__($localization->buildNamespaceFor($path) . '::errors.forbidden'))->toBe('forbidden'); } $this->app->setLocale('fa'); - foreach ($configuration->paths() as $path) { - expect(__($configuration->buildNamespaceFor($path) . '::errors.forbidden'))->toBe('ممنوع'); + foreach ($localization->paths() as $path) { + expect(__($localization->buildNamespaceFor($path) . '::errors.forbidden'))->toBe('ممنوع'); } }); diff --git a/tests/Functional/Foundation/Support/Providers/MigrationServiceProviderTest.php b/tests/Functional/Foundation/Support/Providers/MigrationServiceProviderTest.php index abb394ae9..d0a8a044f 100644 --- a/tests/Functional/Foundation/Support/Providers/MigrationServiceProviderTest.php +++ b/tests/Functional/Foundation/Support/Providers/MigrationServiceProviderTest.php @@ -1,5 +1,7 @@ merge([ 'include' => 'children.books,parent', ]); - $this->repository = new class extends UserRepository { + $this->repository = new class () extends UserRepository { public function shouldEagerLoadIncludes(): bool { return true; diff --git a/tests/Functional/Macros/MacroServiceProviderTest.php b/tests/Functional/Macros/MacroServiceProviderTest.php index bb2124111..c9b3059a7 100644 --- a/tests/Functional/Macros/MacroServiceProviderTest.php +++ b/tests/Functional/Macros/MacroServiceProviderTest.php @@ -1,6 +1,6 @@ afterEach(function () { +pest()->afterEach(function (): void { $this->freezeTime(); }); /* diff --git a/tests/TestCase.php b/tests/TestCase.php index 004eed958..a01472af7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,5 +1,7 @@ createOne([ - 'name' => $name, + 'name' => $name, 'email' => $email, ]); diff --git a/tests/Unit/Core/Models/BaseModelTest.php b/tests/Unit/Core/Models/BaseModelTest.php index d8faf00c3..b1f8d818d 100644 --- a/tests/Unit/Core/Models/BaseModelTest.php +++ b/tests/Unit/Core/Models/BaseModelTest.php @@ -1,5 +1,7 @@ model = new class extends BaseModel { + $this->model = new class () extends BaseModel { protected $resourceKey = 'custom-resource-key'; }; }); @@ -26,10 +28,10 @@ it('can locate the factory of the model using different call styles', function (): void { $usingModel = Book::factory()->makeOne(); - $usingFactory = Book::factory()->makeOne(); + $model = Book::factory()->makeOne(); expect($usingModel)->toBeInstanceOf(Book::class) - ->and($usingFactory)->toBeInstanceOf(Book::class); + ->and($model)->toBeInstanceOf(Book::class); }); describe('getHashedKey()', function (): void { @@ -38,35 +40,35 @@ }); it('returns hashed primary key by default', function (): void { - $book = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - expect($book->getHashedKey())->toBe(hashids()->encode($book->getKey())); + expect($model->getHashedKey())->toBe(hashids()->encode($model->getKey())); }); it('can return hashed key for a specific field', function (): void { - $book = Book::factory()->makeOne(); + $model = Book::factory()->makeOne(); - expect($book->getHashedKey('author_id'))->toBe(hashids()->encode($book->author_id)); + expect($model->getHashedKey('author_id'))->toBe(hashids()->encode($model->author_id)); }); it('returns null if the field is null', function (): void { - $book = Book::factory()->makeOne(['author_id' => null]); + $model = Book::factory()->makeOne(['author_id' => null]); - expect($book->getHashedKey('author_id'))->toBeNull(); + expect($model->getHashedKey('author_id'))->toBeNull(); }); it('returns the original field value if hash-id is disabled', function (): void { config(['apiato.hash-id' => false]); - $book = Book::factory()->makeOne(); + $model = Book::factory()->makeOne(); - expect($book->getHashedKey())->toEqual($book->getKey()); + expect($model->getHashedKey())->toEqual($model->getKey()); }); it('returns the original field value if the field is not hashable and hash-id is disabled', function (): void { config(['apiato.hash-id' => false]); - $book = Book::factory()->makeOne(); + $model = Book::factory()->makeOne(); - expect($book->getHashedKey('title'))->toEqual($book->title); + expect($model->getHashedKey('title'))->toEqual($model->title); }); }); @@ -74,12 +76,12 @@ it('can handle hashed ids', function (): void { config(['apiato.hash-id' => true]); Book::factory()->count(3)->create(); - $target = Book::factory()->createOne(); + $model = Book::factory()->createOne(); expect( Book::newModelInstance()->resolveRouteBinding( - hashids()->encode($target->getKey()), - )->is($target), + hashids()->encode($model->getKey()), + )->is($model), )->toBeTrue(); }); @@ -90,9 +92,9 @@ function (bool $enabled, bool $incrementing, bool $isHashedId, bool $expectation expect( Book::newModelInstance() - ->setIncrementing($incrementing) - ->shouldProcessHashIdRouteBinding(!$isHashedId ?: hashids()->encode(1)), - )->toBe($expectation, "Enabled: {$enabled}, Incrementing: {$incrementing}"); + ->setIncrementing($incrementing) + ->shouldProcessHashIdRouteBinding(!$isHashedId ?: hashids()->encode(1)), + )->toBe($expectation, sprintf('Enabled: %s, Incrementing: %s', $enabled, $incrementing)); }, )->with([ [true, true, true, true], @@ -118,7 +120,7 @@ function (bool $enabled, bool $incrementing, bool $isHashedId, bool $expectation it( 'can detect when it should resolve hashed ids and when it should not 2', - function (string $value, string|null $field, Book $target): void { + function (string $value, null|string $field, Book $target): void { config(['apiato.hash-id' => true]); expect( @@ -130,39 +132,39 @@ function (string $value, string|null $field, Book $target): void { }, )->with([ function (): array { - $target = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - return [hashids()->encode($target->id), null, $target]; + return [hashids()->encode($model->id), null, $model]; }, function (): array { - $target = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - return [hashids()->encode($target->id), 'id', $target]; + return [hashids()->encode($model->id), 'id', $model]; }, function (): array { - $target = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - return [$target->title, 'title', $target]; + return [$model->title, 'title', $model]; }, ])->skip(); it('can handle unhashed ids', function (): void { config(['apiato.hash-id' => false]); Book::factory()->count(3)->create(); - $target = Book::factory()->createOne([ + $model = Book::factory()->createOne([ 'title' => 'Target', ]); expect( Book::newModelInstance()->resolveRouteBinding( - $target->getKey(), - )->is($target), + $model->getKey(), + )->is($model), )->toBeTrue() ->and( Book::newModelInstance()->resolveRouteBinding( - $target->title, + $model->title, 'title', - )->is($target), + )->is($model), )->toBeTrue(); }); }); diff --git a/tests/Unit/Core/Repositories/Exceptions/ResourceCreationFailedTest.php b/tests/Unit/Core/Repositories/Exceptions/ResourceCreationFailedTest.php index 9ea5918f6..597580a5c 100644 --- a/tests/Unit/Core/Repositories/Exceptions/ResourceCreationFailedTest.php +++ b/tests/Unit/Core/Repositories/Exceptions/ResourceCreationFailedTest.php @@ -1,14 +1,14 @@ toBeInstanceOf(ResourceCreationFailed::class); - expect($exception->getMessage())->toBe('Resource creation failed.'); - expect($exception->getStatusCode())->toBe(417); + $resourceCreationFailed = ResourceCreationFailed::create(); + expect($resourceCreationFailed)->toBeInstanceOf(ResourceCreationFailed::class); + expect($resourceCreationFailed->getMessage())->toBe('Resource creation failed.'); + expect($resourceCreationFailed->getStatusCode())->toBe(417); }); })->covers(ResourceCreationFailed::class); diff --git a/tests/Unit/Core/Repositories/Exceptions/ResourceNotFoundTest.php b/tests/Unit/Core/Repositories/Exceptions/ResourceNotFoundTest.php index 9c2ded7f7..58485f21a 100644 --- a/tests/Unit/Core/Repositories/Exceptions/ResourceNotFoundTest.php +++ b/tests/Unit/Core/Repositories/Exceptions/ResourceNotFoundTest.php @@ -1,14 +1,14 @@ toBeInstanceOf(ResourceNotFound::class); - expect($exception->getMessage())->toBe('Resource not found.'); - expect($exception->getStatusCode())->toBe(404); + $resourceNotFound = ResourceNotFound::create(); + expect($resourceNotFound)->toBeInstanceOf(ResourceNotFound::class); + expect($resourceNotFound->getMessage())->toBe('Resource not found.'); + expect($resourceNotFound->getStatusCode())->toBe(404); }); })->covers(ResourceNotFound::class); diff --git a/tests/Unit/Core/Repositories/RepositoryTest.php b/tests/Unit/Core/Repositories/RepositoryTest.php index 1a73edf75..6c3045d2c 100644 --- a/tests/Unit/Core/Repositories/RepositoryTest.php +++ b/tests/Unit/Core/Repositories/RepositoryTest.php @@ -1,40 +1,163 @@ addRequestCriteria(); + + expect($bookRepository->getCriteria())->toHaveCount(1) + ->and($bookRepository->getCriteria()->first())->toBeInstanceOf(RequestCriteria::class); + }); + + it('can remove request criteria', function (): void { + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $bookRepository = $repository->removeRequestCriteria(); + + expect($bookRepository->getCriteria())->toBeEmpty(); + }); + + it('decodes search query when hash-id is enabled', function (): void { + config(['apiato.hash-id' => true]); + + $hashedId = hashids()->encode(123); + $searchString = 'id:' . $hashedId; + $expectedString = 'id:123'; + + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['search' => $searchString]); + app()->instance(Request::class, $request); + + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $actualQuery = app(Request::class)->query('search'); + + expect($actualQuery)->toBe($expectedString); + }); + + it('does not decode search query when hash-id is disabled', function (): void { + config(['apiato.hash-id' => false]); + + $hashedId = hashids()->encode(123); + $searchString = 'id:' . $hashedId; + + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['search' => $searchString]); + app()->instance(Request::class, $request); + + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $actualQuery = app(Request::class)->query('search'); + + expect($actualQuery)->toBe($searchString); + }); + + it('ignores boolean values when decoding search query', function (): void { + config(['apiato.hash-id' => true]); + + $searchString = 'is_active:true;is_admin:false;status:1;flag:0'; + + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['search' => $searchString]); + app()->instance(Request::class, $request); + + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $actualQuery = app(Request::class)->query('search'); + + expect($actualQuery)->toBe($searchString); + }); + + it('ignores numeric values when decoding search query', function (): void { + config(['apiato.hash-id' => true]); + + $searchString = 'age:30;count:5'; + + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['search' => $searchString]); + app()->instance(Request::class, $request); + + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $actualQuery = app(Request::class)->query('search'); + + expect($actualQuery)->toBe($searchString); + }); + + it('handles complex search input with mixed values', function (): void { + config(['apiato.hash-id' => true]); + + $hashedId1 = hashids()->encode(123); + $hashedId2 = hashids()->encode(456); + $searchString = sprintf('id:%s;name:John;is_active:true;role_id:%s;age:30', $hashedId1, $hashedId2); + $expectedString = 'id:123;name:John;is_active:true;role_id:456;age:30'; + + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['search' => $searchString]); + app()->instance(Request::class, $request); + + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $actualQuery = app(Request::class)->query('search'); + + expect($actualQuery)->toBe($expectedString); + }); + + it('does nothing when search parameter is empty', function (): void { + config(['apiato.hash-id' => true]); - $result = $repository->addRequestCriteria(); + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['search' => '']); + app()->instance(Request::class, $request); + + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $actualQuery = app(Request::class)->query('search'); + + expect($actualQuery)->toBe(''); + }); - expect($result->getCriteria())->toHaveCount(1) - ->and($result->getCriteria()->first())->toBeInstanceOf(RequestCriteria::class); + it('does nothing when search parameter is not a string', function (): void { + config(['apiato.hash-id' => true]); - $result = $repository->removeRequestCriteria(); + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['search' => null]); + app()->instance(Request::class, $request); - expect($result->getCriteria())->toBeEmpty(); + $repository = new BookRepository(); + $repository->addRequestCriteria(); + + $actualQuery = app(Request::class)->query('search'); + + expect($actualQuery)->toBeNull(); + }); }); it('can push criteria and passing args', function (): void { $repository = new BookRepository(); - $result = $repository->pushCriteriaWith(RequestCriteria::class, ['criteria' => request()]); + $bookRepository = $repository->pushCriteriaWith(RequestCriteria::class, ['criteria' => app(Request::class)]); - expect($result->getCriteria())->toHaveCount(1) - ->and($result->getCriteria()->first())->toBeInstanceOf(RequestCriteria::class); + expect($bookRepository->getCriteria())->toHaveCount(1) + ->and($bookRepository->getCriteria()->first())->toBeInstanceOf(RequestCriteria::class); }); it('discover its model', function (): void { @@ -43,28 +166,28 @@ expect($repository->model())->toBe(Book::class); }); - it('can cache method call results', function (string $method, \Closure $args): void { + it('can cache method call results', function (string $method, Closure $args): void { Cache::clear(); config(['repository.cache.enabled' => true]); - $user = User::factory()->createOne(); + $model = User::factory()->createOne(); $repository = $this->app->make(UserRepository::class); - $arguments = $args($user->id); + $arguments = $args($model->id); $cacheKey = $repository->getCacheKey($method, [$arguments]); expect(Cache::missing($cacheKey))->toBeTrue(); // hit the cache - $repository->$method($arguments); + $repository->{$method}($arguments); expect(Cache::has($cacheKey))->toBeTrue(); })->with([ 'all' => [ 'all', - static fn () => ['*'], + static fn (): array => ['*'], ], 'paginate' => [ 'paginate', - static fn () => null, + static fn (): int => 0, ], 'find' => [ 'find', @@ -72,17 +195,17 @@ ], 'findByField' => [ 'findByField', - static fn ($id) => ['id', $id], + static fn ($id): array => ['id', $id], ], 'findWhere' => [ 'findWhere', - static fn ($id) => [$id], + static fn ($id): array => [$id], ], ]); describe('scopes', function (): void { it('can push/reset scopes stack', function (): void { - $repository = new class extends UserRepository { + $repository = new class () extends UserRepository { public function shouldEagerLoadIncludes(): bool { return false; @@ -130,30 +253,30 @@ public function getScopes(): array it('can make new model instance', function (): void { $repository = new BookRepository(); - $result = $repository->make(['title' => 'test']); + $book = $repository->make(['title' => 'test']); - expect($result)->toBeInstanceOf(Book::class) - ->and($result->title)->toBe('test'); + expect($book)->toBeInstanceOf(Book::class) + ->and($book->title)->toBe('test'); }); it('can get the model instance', function (): void { $repository = new BookRepository(); - $result = $repository->getModel(); + $book = $repository->getModel(); - expect($result)->toBeInstanceOf(Book::class); + expect($book)->toBeInstanceOf(Book::class); }); it('can store a new model instance', function ($data): void { $repository = new BookRepository(); - $result = $repository->store($data); + $book = $repository->store($data); - expect($result)->toBeInstanceOf(Book::class) - ->and($result->title)->toBe('test'); + expect($book)->toBeInstanceOf(Book::class) + ->and($book->title)->toBe('test'); })->with([ 'array' => [ - fn () => ['title' => 'test'], + fn (): array => ['title' => 'test'], ], 'model' => [ fn () => Book::factory()->makeOne(['title' => 'test']), @@ -164,10 +287,10 @@ public function getScopes(): array it('can create a new model instance', function (): void { $repository = new BookRepository(); - $result = $repository->create(['title' => 'test']); + $book = $repository->create(['title' => 'test']); - expect($result)->toBeInstanceOf(Book::class) - ->and($result->title)->toBe('test'); + expect($book)->toBeInstanceOf(Book::class) + ->and($book->title)->toBe('test'); }); it('throws custom exception', function (): void { @@ -181,36 +304,36 @@ public function getScopes(): array it('can save the model instance', function (): void { $repository = new BookRepository(); - $book = Book::factory()->makeOne(); + $model = Book::factory()->makeOne(); - $this->assertModelMissing($book); + $this->assertModelMissing($model); - $repository->save($book); + $repository->save($model); - $this->assertModelExists($book); + $this->assertModelExists($model); }); describe('first or create', function (): void { it('can find first or create', function (): void { $repository = new BookRepository(); - $book = Book::factory()->makeOne(); + $model = Book::factory()->makeOne(); - $this->assertModelMissing($book); + $this->assertModelMissing($model); - $result = $repository->firstOrCreate(['title' => $book->title], $book->toArray()); + $book = $repository->firstOrCreate(['title' => $model->title], $model->toArray()); - expect($result)->toBeInstanceOf(Book::class) - ->and($result->title)->toBe($book->title); + expect($book)->toBeInstanceOf(Book::class) + ->and($book->title)->toBe($model->title); - $this->assertModelExists($result); + $this->assertModelExists($book); }); it('can update attributes if model is found', function (): void { $repository = new UserRepository(); - $result = $repository->firstOrCreate( + $model = $repository->firstOrCreate( [ - 'email' => 'saruman@the.white', + 'email' => 'saruman@the.white', 'password' => 'password', ], [ @@ -218,21 +341,21 @@ public function getScopes(): array ], ); - expect($result)->toBeInstanceOf(User::class) - ->and($result->name)->toBe('saruman') - ->and($result->email)->toBe('saruman@the.white'); + expect($model)->toBeInstanceOf(User::class) + ->and($model->name)->toBe('saruman') + ->and($model->email)->toBe('saruman@the.white'); }); }); describe('update', function (): void { it('can update an entity instance', function (): void { $repository = new BookRepository(); - $book = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - $result = $repository->update(['title' => 'updated'], $book->id); + $book = $repository->update(['title' => 'updated'], $model->id); - $this->assertModelExists($result); - expect($result->title)->toBe('updated'); + $this->assertModelExists($book); + expect($book->title)->toBe('updated'); }); it('throws custom exception', function (): void { @@ -247,12 +370,12 @@ public function getScopes(): array describe('findOrFail', function (): void { it('can find an entity instance', function (): void { $repository = new BookRepository(); - $book = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - $result = $repository->findOrFail($book->id); + $book = $repository->findOrFail($model->id); - expect($result)->toBeInstanceOf(Book::class) - ->and($result->id)->toBe($book->id); + expect($book)->toBeInstanceOf(Book::class) + ->and($book->id)->toBe($model->id); }); it('throws custom exception', function (): void { @@ -267,12 +390,12 @@ public function getScopes(): array describe('find', function (): void { it('can find an entity instance', function (): void { $repository = new BookRepository(); - $book = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - $result = $repository->find($book->id); + $result = $repository->find($model->id); expect($result)->toBeInstanceOf(Book::class) - ->and($result->id)->toBe($book->id); + ->and($result?->getKey())->toBe($model->id); }); it('returns null if model is not found', function (): void { @@ -287,12 +410,12 @@ public function getScopes(): array describe('findById', function (): void { it('can find an entity instance', function (): void { $repository = new BookRepository(); - $book = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - $result = $repository->findById($book->id); + $result = $repository->findById($model->id); expect($result)->toBeInstanceOf(Book::class) - ->and($result->id)->toBe($book->id); + ->and($result?->getKey())->toBe($model->id); }); it('returns null if model is not found', function (): void { @@ -377,12 +500,12 @@ public function getScopes(): array describe('delete', function (): void { it('can delete an entity instance', function (): void { $repository = new BookRepository(); - $book = Book::factory()->createOne(); + $model = Book::factory()->createOne(); - $result = $repository->delete($book->id); + $result = $repository->delete($model->id); expect($result)->toBeTrue(); - $this->assertModelMissing($book); + $this->assertModelMissing($model); }); it('throws custom exception', function (): void { @@ -392,5 +515,73 @@ public function getScopes(): array $repository->findOrFail(777); })->toThrow(ResourceNotFound::create('Book')); }); + + it('throws exception when deleting non-existent entity', function (): void { + expect(function (): void { + $repository = new BookRepository(); + + $repository->delete(777); + })->toThrow(Error::class); + }); + }); + + describe('pagination handling', function (): void { + it('can set pagination limit from request', function (): void { + $repository = new BookRepository(); + + $request = Request::create('/', SymfonyRequest::METHOD_GET, ['limit' => '42']); + app()->instance(Request::class, $request); + + $limit = $repository->setPaginationLimit(42); + + expect($limit)->toBe(42); + }); + + it('can set pagination limit from parameter', function (): void { + $repository = new BookRepository(); + + $limit = $repository->setPaginationLimit(42); + + expect($limit)->toBe(42); + }); + + it('can check if pagination should be skipped', function (): void { + $repository = new BookRepository(); + + expect($repository->wantsToSkipPagination(0))->toBeTrue(); + expect($repository->wantsToSkipPagination(5))->toBeFalse(); + }); + + it('checks if disable pagination is allowed via repository property', function (): void { + $repository = mock(UserRepository::class)->makePartial(); + $repository->shouldReceive('canSkipPagination')->andReturn(true); + + expect($repository->canSkipPagination())->toBeTrue(); + + $repository = mock(UserRepository::class)->makePartial(); + $repository->shouldReceive('canSkipPagination')->andReturn(false); + + expect($repository->canSkipPagination())->toBeFalse(); + }); + + it('checks if disable pagination is allowed via global config', function (): void { + $repository = new BookRepository(); + + config(['repository.pagination.skip' => true]); + expect($repository->canSkipPagination())->toBeTrue(); + + config(['repository.pagination.skip' => false]); + expect($repository->canSkipPagination())->toBeFalse(); + }); + + it('can check if limit exceeds max pagination limit', function (): void { + $legacyMock = mock(BookRepository::class)->makePartial(); + $legacyMock->allows('exceedsMaxPaginationLimit')->andReturnUsing( + fn ($limit): bool => $limit > 10 + ); + + expect($legacyMock->exceedsMaxPaginationLimit(20))->toBeTrue(); + expect($legacyMock->exceedsMaxPaginationLimit(5))->toBeFalse(); + }); }); })->covers(Repository::class); diff --git a/tests/Unit/Core/Requests/RequestTest.php b/tests/Unit/Core/Requests/RequestTest.php index 7cbf83507..764cea129 100644 --- a/tests/Unit/Core/Requests/RequestTest.php +++ b/tests/Unit/Core/Requests/RequestTest.php @@ -1,16 +1,16 @@ merge([ 'name' => 'Gandalf', - 'age' => 100, + 'age' => 100, ]); $result = $sut->sanitize(['age']); diff --git a/tests/Unit/Core/Tests/TestCaseTest.php b/tests/Unit/Core/Tests/TestCaseTest.php index f0071698f..ed8b482f4 100644 --- a/tests/Unit/Core/Tests/TestCaseTest.php +++ b/tests/Unit/Core/Tests/TestCaseTest.php @@ -1,12 +1,13 @@ sut = new class('Anonym') extends TestCase {}; + $this->sut = new class ('Anonym') extends TestCase { + }; }); describe(class_basename(TestCase::class), function (): void { it('uses expected traits', function (): void { diff --git a/tests/Unit/Core/Transformers/TransformerTest.php b/tests/Unit/Core/Transformers/TransformerTest.php index 3014ff14d..c6b4df83e 100644 --- a/tests/Unit/Core/Transformers/TransformerTest.php +++ b/tests/Unit/Core/Transformers/TransformerTest.php @@ -1,5 +1,7 @@ [null, 'Book'], + 'null resource key' => [null, 'Book'], 'override resource key' => ['CustomKey', 'CustomKey'], ]); - it('can return an item', function (string|null $resourceKey, $expected): void { + it('can return an item', function (null|string $resourceKey, $expected): void { $transformer = new BookTransformer(); $transformer->setDefaultIncludes(['author']); @@ -39,7 +41,7 @@ ->and($item->getResourceKey())->toBe($expected); })->with('resourceKeys'); - it('can return a collection', function (string|null $resourceKey, $expected): void { + it('can return a collection', function (null|string $resourceKey, $expected): void { $transformer = new BookTransformer(); $transformer->setDefaultIncludes(['author']); diff --git a/tests/Unit/Foundation/ApiatoTest.php b/tests/Unit/Foundation/ApiatoTest.php index b8fabf74a..0ba28dc97 100644 --- a/tests/Unit/Foundation/ApiatoTest.php +++ b/tests/Unit/Foundation/ApiatoTest.php @@ -1,5 +1,7 @@ create(); @@ -92,7 +96,7 @@ }); it('can be instantiated without a path', function (): void { - $basePath = Safe\realpath(__DIR__ . '/../../../workbench'); + $basePath = realpath(__DIR__ . '/../../../workbench'); $apiato = Apiato::configure()->create(); @@ -100,9 +104,11 @@ }); it('can infer base path', function (): void { - $basePath = Safe\realpath(__DIR__ . '/../../..'); + $basePath = realpath(__DIR__ . '/../../..'); + + $expectation = Apiato::inferBasePath(); - expect(Apiato::inferBasePath())->toBe($basePath); + expect($expectation)->toBe($basePath); }); it('accepts and apply routing config override', function (): void { diff --git a/tests/Unit/Foundation/Configuration/FactoryTest.php b/tests/Unit/Foundation/Configuration/FactoryTest.php index 55d2ee94a..234be9d73 100644 --- a/tests/Unit/Foundation/Configuration/FactoryTest.php +++ b/tests/Unit/Foundation/Configuration/FactoryTest.php @@ -1,5 +1,7 @@ toArray()) - ->filter(static fn (string $provider): bool => StrayServiceProvider::class === $provider) + ->filter(static fn (string $provider): bool => $provider === StrayServiceProvider::class) ->count(), )->toBe(1); }); diff --git a/tests/Unit/Foundation/Configuration/RepositoryTest.php b/tests/Unit/Foundation/Configuration/RepositoryTest.php index 18ad68256..098139cd8 100644 --- a/tests/Unit/Foundation/Configuration/RepositoryTest.php +++ b/tests/Unit/Foundation/Configuration/RepositoryTest.php @@ -1,5 +1,7 @@ withSeeders( static fn (Seeding $seeding): Seeding => $seeding->sortUsing( static fn (): array => [ - (new class extends Seeder { + (new class () extends Seeder { public function run(): void { User::factory()->createOne([ diff --git a/tests/Unit/Foundation/HelpersTest.php b/tests/Unit/Foundation/HelpersTest.php index a76655967..ea5aaeaf0 100644 --- a/tests/Unit/Foundation/HelpersTest.php +++ b/tests/Unit/Foundation/HelpersTest.php @@ -1,14 +1,21 @@ toBeInstanceOf(Apiato::class); })->coversFunction('apiato'); - it('can get the path to the application\'s shared directory', function (): void { + it("can get the path to the application's shared directory", function (): void { expect(shared_path())->toBe(base_path('app/Ship')); })->coversFunction('shared_path'); @@ -16,19 +23,19 @@ expect(hashids())->toBeInstanceOf(HashidsManagerDecorator::class); })->coversFunction('hashids'); - it('can find php files recursively', function () { + it('can find php files recursively', function (): void { $baseDir = sys_get_temp_dir() . '/test_' . uniqid('', true); - \Safe\mkdir($baseDir . '/subdir', 0777, true); - \Safe\file_put_contents($baseDir . '/file1.php', 'toHaveCount(2); // Clean up - \Safe\unlink($baseDir . '/file1.php'); - \Safe\unlink($baseDir . '/subdir/file2.php'); - \Safe\rmdir($baseDir . '/subdir'); - \Safe\rmdir($baseDir); + unlink($baseDir . '/file1.php'); + unlink($baseDir . '/subdir/file2.php'); + rmdir($baseDir . '/subdir'); + rmdir($baseDir); }); }); diff --git a/tests/Unit/Http/Middleware/ProcessETagTest.php b/tests/Unit/Http/Middleware/ProcessETagTest.php index b43c7a339..f58dcdd40 100644 --- a/tests/Unit/Http/Middleware/ProcessETagTest.php +++ b/tests/Unit/Http/Middleware/ProcessETagTest.php @@ -1,5 +1,7 @@ true]); $request = Request::create('/test', 'GET'); $request->headers->remove('Accept'); + $middleware = new ValidateJsonContent(); $this->expectException(RuntimeException::class); @@ -25,6 +28,7 @@ config(['apiato.requests.force-accept-header' => $enabled]); $request = Request::create('/test', 'GET'); $request->headers->set('Accept', 'application/json'); + $middleware = new ValidateJsonContent(); $response = $middleware->handle($request, $this->next); diff --git a/tests/Unit/Http/RequestRelationTest.php b/tests/Unit/Http/RequestRelationTest.php index 8ce42c254..cd0265d55 100644 --- a/tests/Unit/Http/RequestRelationTest.php +++ b/tests/Unit/Http/RequestRelationTest.php @@ -1,6 +1,6 @@ getResourceKey())->toBe($expectation, json_encode($data)); @@ -23,9 +25,19 @@ function (User|LaravelCollection|stdClass|array|Iterator|null $data, string $exp fn (): array => [[User::factory()->makeOne()], 'User'], [fn () => User::factory(2)->make(), 'User'], [fn () => User::factory(2)->make()->getIterator(), 'User'], - [[new class {}], ''], - [fn () => collect([new class {}]), ''], - [fn () => collect([new class {}])->getIterator(), ''], + [ + [new class () { + }, + ], '', + ], + [fn () => collect([new class () { + }, + ]), '', + ], + [fn () => collect([new class () { + }, + ])->getIterator(), '', + ], [fn () => User::factory()->makeOne(), 'User'], [fn (): stdClass => new stdClass(), ''], ]); diff --git a/tests/Unit/Http/Resources/ItemTest.php b/tests/Unit/Http/Resources/ItemTest.php index defba1114..fbc08d782 100644 --- a/tests/Unit/Http/Resources/ItemTest.php +++ b/tests/Unit/Http/Resources/ItemTest.php @@ -1,5 +1,7 @@ getResourceKey())->toBe($expectation, json_encode($data)); @@ -23,9 +25,19 @@ function (User|LaravelCollection|stdClass|array|Iterator|null $data, string $exp fn (): array => [[User::factory()->makeOne()], ''], fn (): array => [User::factory(2)->make(), ''], fn (): array => [User::factory(2)->make()->getIterator(), ''], - [[new class {}], ''], - fn (): array => [collect([new class {}]), ''], - fn (): array => [collect([new class {}])->getIterator(), ''], + [ + [new class () { + }, + ], '', + ], + fn (): array => [collect([new class () { + }, + ]), '', + ], + fn (): array => [collect([new class () { + }, + ])->getIterator(), '', + ], fn (): array => [User::factory()->makeOne(), 'User'], fn (): array => [new stdClass(), ''], ]); diff --git a/tests/Unit/Http/ResponseTest.php b/tests/Unit/Http/ResponseTest.php index e4f909196..31baf2479 100644 --- a/tests/Unit/Http/ResponseTest.php +++ b/tests/Unit/Http/ResponseTest.php @@ -1,6 +1,6 @@ manager(); + $manager = $response->manager(); - expect($result)->toBeInstanceOf(Manager::class); + expect($manager)->toBeInstanceOf(Manager::class); }); it('can handle CSV includes for single resource', function (string $include, array $expected): void { @@ -43,26 +43,26 @@ function getUser(): User $response = Response::create(getUser()); $response->transformWith(UserTransformer::class); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); foreach ($expected as $expectation) { - $result->has($expectation); + $assertableJson->has($expectation); } })->with([ 'single string' => [ - 'include' => 'parent', + 'include' => 'parent', 'expected' => ['data.parent'], ], 'single string nested' => [ - 'include' => 'children.books', + 'include' => 'children.books', 'expected' => ['data.children.data.0.books'], ], 'csv string' => [ - 'include' => 'parent,children', + 'include' => 'parent,children', 'expected' => ['data.parent', 'data.children'], ], 'csv string and nested' => [ - 'include' => 'parent,children.books', + 'include' => 'parent,children.books', 'expected' => ['data.parent', 'data.children.data.0.books'], ], ]); @@ -72,22 +72,22 @@ function getUser(): User $response = Response::create(getUser()); $response->transformWith(UserTransformer::class); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); foreach ($expected as $expectation) { - $result->has($expectation); + $assertableJson->has($expectation); } })->with([ 'single array' => [ - 'include' => ['parent'], + 'include' => ['parent'], 'expected' => ['data.parent'], ], 'multiple array' => [ - 'include' => ['parent', 'children'], + 'include' => ['parent', 'children'], 'expected' => ['data.parent', 'data.children'], ], 'multiple array nested' => [ - 'include' => ['parent.books', 'children'], + 'include' => ['parent.books', 'children'], 'expected' => ['data.parent.data.books', 'data.children'], ], ]); @@ -98,9 +98,9 @@ function getUser(): User $users = app(UserRepository::class, ['app' => $this->app])->paginate(); $response = Response::create($users)->transformWith(UserTransformer::class); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); - $result->has('meta.include', fn (AssertableJson $json): AssertableJson => $json->whereAll(['parent', 'children', 'books'])); + $assertableJson->has('meta.include', fn (AssertableJson $json): AssertableJson => $json->whereAll(['parent', 'children', 'books'])); })->with([ 'single string' => [ 'include' => 'parent', @@ -130,45 +130,46 @@ function getUser(): User $response = Response::create(getUser()); $response->transformWith(UserTransformer::class); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); foreach ($expected as $expectation) { - $result->has($expectation); - $result->has('meta.include', fn (AssertableJson $json): AssertableJson => $json->whereAll(['parent', 'children', 'books'])); + $assertableJson->has($expectation); + $assertableJson->has('meta.include', fn (AssertableJson $json): AssertableJson => $json->whereAll(['parent', 'children', 'books'])); } + foreach ($missing as $expectation) { - $result->missing($expectation); + $assertableJson->missing($expectation); } })->with([ 'without includes' => [ - 'fields' => ['User' => 'id,email'], + 'fields' => ['User' => 'id,email'], 'expected' => ['data.id', 'data.email'], - 'missing' => ['data.type', 'data.name', 'data.created_at', 'data.updated_at', 'data.children', 'data.books'], + 'missing' => ['data.type', 'data.name', 'data.created_at', 'data.updated_at', 'data.children', 'data.books'], ], 'only filter nested include keys' => [ - 'fields' => ['Book' => 'author,title'], + 'fields' => ['Book' => 'author,title'], 'expected' => ['data.type', 'data.id', 'data.email', 'data.name', 'data.created_at', 'data.updated_at', 'data.books.data.0.author', 'data.books.data.0.title'], - 'missing' => ['data.books.data.0.id', 'data.books.data.0.created_at', 'data.books.data.0.updated_at'], + 'missing' => ['data.books.data.0.id', 'data.books.data.0.created_at', 'data.books.data.0.updated_at'], ], 'with first level includes - no filter' => [ - 'fields' => ['User' => 'type,id,email,books'], + 'fields' => ['User' => 'type,id,email,books'], 'expected' => ['data.type', 'data.id', 'data.email', 'data.books.data.0.type', 'data.books.data.0.id', 'data.books.data.0.title', 'data.books.data.0.author', 'data.books.data.0.created_at', 'data.books.data.0.updated_at'], - 'missing' => ['data.name', 'data.created_at', 'data.updated_at'], + 'missing' => ['data.name', 'data.created_at', 'data.updated_at'], ], 'with first level includes - filter' => [ - 'fields' => ['User' => 'type,id,email,books', 'Book' => 'type,author'], + 'fields' => ['User' => 'type,id,email,books', 'Book' => 'type,author'], 'expected' => ['data.type', 'data.id', 'data.email', 'data.books.data.0.type', 'data.books.data.0.author'], - 'missing' => ['data.children', 'data.books.data.0.id', 'data.books.data.0.title', 'data.books.data.0.created_at', 'data.books.data.0.updated_at', 'data.name', 'data.created_at', 'data.updated_at'], + 'missing' => ['data.children', 'data.books.data.0.id', 'data.books.data.0.title', 'data.books.data.0.created_at', 'data.books.data.0.updated_at', 'data.name', 'data.created_at', 'data.updated_at'], ], 'with nested includes - no filter' => [ - 'fields' => ['User' => 'type,id,email,children,books'], + 'fields' => ['User' => 'type,id,email,children,books'], 'expected' => ['data.type', 'data.id', 'data.email', 'data.children.data.0.type', 'data.children.data.0.id', 'data.children.data.0.email', 'data.children.data.0.books.data.0.type', 'data.children.data.0.books.data.0.id', 'data.children.data.0.books.data.0.title', 'data.children.data.0.books.data.0.author', 'data.children.data.0.books.data.0.created_at', 'data.children.data.0.books.data.0.updated_at'], - 'missing' => ['data.name', 'data.created_at', 'data.updated_at'], + 'missing' => ['data.name', 'data.created_at', 'data.updated_at'], ], 'with nested includes - filter' => [ - 'fields' => ['User' => 'id,email,children,books', 'Book' => 'id'], + 'fields' => ['User' => 'id,email,children,books', 'Book' => 'id'], 'expected' => ['data.id', 'data.email', 'data.children.data.0.id', 'data.children.data.0.email', 'data.children.data.0.books.data.0.id'], - 'missing' => ['data.type', 'data.children.data.0.type', 'data.children.data.0.books.data.0.type', 'data.children.data.0.books.data.0.title', 'data.children.data.0.books.data.0.author', 'data.children.data.0.books.data.0.created_at', 'data.children.data.0.books.data.0.updated_at', 'data.name', 'data.created_at', 'data.updated_at'], + 'missing' => ['data.type', 'data.children.data.0.type', 'data.children.data.0.books.data.0.type', 'data.children.data.0.books.data.0.title', 'data.children.data.0.books.data.0.author', 'data.children.data.0.books.data.0.created_at', 'data.children.data.0.books.data.0.updated_at', 'data.name', 'data.created_at', 'data.updated_at'], ], ]); @@ -177,26 +178,26 @@ function getUser(): User $response = Response::create(getUser()); $response->transformWith(UserTransformer::class)->parseIncludes($exclude); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); foreach ($expected as $expectation) { - $result->missing($expectation); + $assertableJson->missing($expectation); } })->with([ 'single string' => [ - 'exclude' => 'parent', + 'exclude' => 'parent', 'expected' => ['data.parent'], ], 'single string nested' => [ - 'exclude' => 'children.books', + 'exclude' => 'children.books', 'expected' => ['data.children.data.0.books'], ], 'csv string' => [ - 'exclude' => 'parent,children', + 'exclude' => 'parent,children', 'expected' => ['data.parent', 'data.children'], ], 'csv string and nested' => [ - 'exclude' => 'parent,children.books', + 'exclude' => 'parent,children.books', 'expected' => ['data.parent', 'data.children.data.0.books'], ], ]); @@ -206,22 +207,22 @@ function getUser(): User $response = Response::create(getUser()); $response->transformWith(UserTransformer::class)->parseIncludes($exclude); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); foreach ($expected as $expectation) { - $result->missing($expectation); + $assertableJson->missing($expectation); } })->with([ 'single array' => [ - 'exclude' => ['parent'], + 'exclude' => ['parent'], 'expected' => ['data.parent'], ], 'multiple array' => [ - 'exclude' => ['parent', 'children'], + 'exclude' => ['parent', 'children'], 'expected' => ['data.parent', 'data.children'], ], 'multiple array nested' => [ - 'exclude' => ['parent.books', 'children'], + 'exclude' => ['parent.books', 'children'], 'expected' => ['data.parent.data.books', 'data.children'], ], ]); @@ -232,9 +233,9 @@ function getUser(): User $users = app(UserRepository::class, ['app' => $this->app])->paginate(); $response = Response::create($users)->transformWith(UserTransformer::class)->parseIncludes($exclude); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); - $result->has('meta.include', fn (AssertableJson $json): AssertableJson => $json->whereAll(['parent', 'children', 'books'])); + $assertableJson->has('meta.include', fn (AssertableJson $json): AssertableJson => $json->whereAll(['parent', 'children', 'books'])); })->with([ 'single string' => [ 'exclude' => 'parent', @@ -265,9 +266,9 @@ function getUser(): User $response->transformWith(UserTransformer::class); $response->withResourceName($resourceName); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); - $result->missing('data.type'); + $assertableJson->missing('data.type'); })->with([ 'empty string' => [ 'resourceName' => '', @@ -277,15 +278,15 @@ function getUser(): User ], ]); - it('can use fallback default resource name', function (bool|null $resourceName): void { + it('can use fallback default resource name', function (null|bool $resourceName): void { request()->merge(['include' => 'books,children.books', 'fields' => [$resourceName => 'id', 'Book' => 'author,title']]); $response = Response::create(getUser()); $response->transformWith(UserTransformer::class); $response->withResourceName($resourceName); - $result = AssertableJson::fromArray($response->toArray()); + $assertableJson = AssertableJson::fromArray($response->toArray()); - $result->has('data.type'); + $assertableJson->has('data.type'); })->with([ 'null' => [ 'resourceName' => null, @@ -298,39 +299,39 @@ function getUser(): User it('can generate 200/OK response', function (): void { $response = Response::create(getUser()); - $result = $response->ok(); + $jsonResponse = $response->ok(); - expect($result)->getStatusCode()->toBe(200)->getData()->toHaveKey('data', []); + expect($jsonResponse)->getStatusCode()->toBe(200)->getData()->toHaveKey('data', []); }); it('can generate 202/Accepted response', function (): void { $response = Response::create(getUser()); - $result = $response->accepted(); + $jsonResponse = $response->accepted(); - expect($result)->getStatusCode()->toBe(202)->getData()->toHaveKey('data', []); + expect($jsonResponse)->getStatusCode()->toBe(202)->getData()->toHaveKey('data', []); }); it('can generate 201/Created response', function (): void { $response = Response::create(getUser()); - $result = $response->created(); + $jsonResponse = $response->created(); - expect($result)->getStatusCode()->toBe(201)->getData()->toHaveKey('data', []); + expect($jsonResponse)->getStatusCode()->toBe(201)->getData()->toHaveKey('data', []); }); it('can generate 204/NoContent response', function (): void { $response = Response::create(getUser()); $response->transformWith(UserTransformer::class); - $result = $response->noContent(); + $jsonResponse = $response->noContent(); - expect($result->getStatusCode())->toBe(204); + expect($jsonResponse->getStatusCode())->toBe(204); }); it('can parse include params with resource name', function (): void { $include = 'books'; - $includeWithParams = "$include:test(2|value)"; + $includeWithParams = $include . ':test(2|value)'; request()->merge(['include' => $includeWithParams]); $response = Response::create(getUser()); $response->transformWith(UserTransformer::class); @@ -347,7 +348,7 @@ function getUser(): User expect($expectedParams)->toEqual($actualParams); }); - it('returns the custom resource class', function (Collection|User|array|null $data, string $expectation): void { + it('returns the custom resource class', function (null|Collection|User|array $data, string $expectation): void { $response = Response::create($data); $result = $response->getResourceClass(); @@ -355,8 +356,8 @@ function getUser(): User expect($result)->toBe($expectation); })->with([ [fn () => User::factory()->makeOne(), Item::class], - [Collection::empty(), \Apiato\Http\Resources\Collection::class], - [[], \Apiato\Http\Resources\Collection::class], + [Collection::empty(), Apiato\Http\Resources\Collection::class], + [[], Apiato\Http\Resources\Collection::class], [null, NullResource::class], ]); diff --git a/tests/Unit/Macros/MacroServiceProviderTest.php b/tests/Unit/Macros/MacroServiceProviderTest.php index 7bed5a760..ba29d92cd 100644 --- a/tests/Unit/Macros/MacroServiceProviderTest.php +++ b/tests/Unit/Macros/MacroServiceProviderTest.php @@ -1,5 +1,7 @@ toBeInstanceOf(Apiato\Http\Response::class); }); - dataset('method_params', function () { + dataset('method_params', function (): array { return [ [null, [], 0], ['foo', [], 0], @@ -31,18 +33,18 @@ expect($result) ->toBeInstanceOf(JsonResponse::class) ->and($result->getData(true)) - ->when(!is_null($data), fn (Expectation $ex) => $ex->toBe($data)) - ->when(is_null($data), fn (Expectation $ex) => $ex->toBeArray()->toBeEmpty()) + ->when(!is_null($data), fn (Expectation $ex): Expectation => $ex->toBe($data)) + ->when(is_null($data), fn (Expectation $ex): Expectation => $ex->toBeArray()->toBeEmpty()) ->and($result->getStatusCode()) ->toBe(200) ->and($result->headers->get('foo')) - ->when([] !== $headers, fn (Expectation $ex) => $ex->toBe('bar')) + ->when($headers !== [], fn (Expectation $ex): Expectation => $ex->toBe('bar')) ->when( - [] === $headers, - fn (Expectation $ex) => $ex->toBeEmpty() - ->and($result->getEncodingOptions()) - ->when(0 !== $options, fn (Expectation $ex) => $ex->toBe(1)) - ->when(0 === $options, fn (Expectation $ex) => $ex->toBe(0)), + $headers === [], + fn (Expectation $ex): Expectation => $ex->toBeEmpty() + ->and($result->getEncodingOptions()) + ->when($options !== 0, fn (Expectation $ex): Expectation => $ex->toBe(1)) + ->when($options === 0, fn (Expectation $ex): Expectation => $ex->toBe(0)), ); })->with('method_params'); @@ -51,23 +53,23 @@ array $headers, int $options, ): void { - $result = Response::accepted($data, $headers, $options); + $jsonResponse = Response::accepted($data, $headers, $options); - expect($result) + expect($jsonResponse) ->toBeInstanceOf(JsonResponse::class) - ->and($result->getData(true)) - ->when(!is_null($data), fn (Expectation $ex) => $ex->toBe($data)) - ->when(is_null($data), fn (Expectation $ex) => $ex->toBeArray()->toBeEmpty()) - ->and($result->getStatusCode()) + ->and($jsonResponse->getData(true)) + ->when(!is_null($data), fn (Expectation $ex): Expectation => $ex->toBe($data)) + ->when(is_null($data), fn (Expectation $ex): Expectation => $ex->toBeArray()->toBeEmpty()) + ->and($jsonResponse->getStatusCode()) ->toBe(202) - ->and($result->headers->get('foo')) - ->when([] !== $headers, fn (Expectation $ex) => $ex->toBe('bar')) + ->and($jsonResponse->headers->get('foo')) + ->when($headers !== [], fn (Expectation $ex): Expectation => $ex->toBe('bar')) ->when( - [] === $headers, - fn (Expectation $ex) => $ex->toBeEmpty() - ->and($result->getEncodingOptions()) - ->when(0 !== $options, fn (Expectation $ex) => $ex->toBe(1)) - ->when(0 === $options, fn (Expectation $ex) => $ex->toBe(0)), + $headers === [], + fn (Expectation $ex): Expectation => $ex->toBeEmpty() + ->and($jsonResponse->getEncodingOptions()) + ->when($options !== 0, fn (Expectation $ex): Expectation => $ex->toBe(1)) + ->when($options === 0, fn (Expectation $ex): Expectation => $ex->toBe(0)), ); })->with('method_params'); @@ -76,23 +78,23 @@ array $headers, int $options, ): void { - $result = Response::created($data, $headers, $options); + $jsonResponse = Response::created($data, $headers, $options); - expect($result) + expect($jsonResponse) ->toBeInstanceOf(JsonResponse::class) - ->and($result->getData(true)) - ->when(!is_null($data), fn (Expectation $ex) => $ex->toBe($data)) - ->when(is_null($data), fn (Expectation $ex) => $ex->toBeArray()->toBeEmpty()) - ->and($result->getStatusCode()) + ->and($jsonResponse->getData(true)) + ->when(!is_null($data), fn (Expectation $ex): Expectation => $ex->toBe($data)) + ->when(is_null($data), fn (Expectation $ex): Expectation => $ex->toBeArray()->toBeEmpty()) + ->and($jsonResponse->getStatusCode()) ->toBe(201) - ->and($result->headers->get('foo')) - ->when([] !== $headers, fn (Expectation $ex) => $ex->toBe('bar')) + ->and($jsonResponse->headers->get('foo')) + ->when($headers !== [], fn (Expectation $ex): Expectation => $ex->toBe('bar')) ->when( - [] === $headers, - fn (Expectation $ex) => $ex->toBeEmpty() - ->and($result->getEncodingOptions()) - ->when(0 !== $options, fn (Expectation $ex) => $ex->toBe(1)) - ->when(0 === $options, fn (Expectation $ex) => $ex->toBe(0)), + $headers === [], + fn (Expectation $ex): Expectation => $ex->toBeEmpty() + ->and($jsonResponse->getEncodingOptions()) + ->when($options !== 0, fn (Expectation $ex): Expectation => $ex->toBe(1)) + ->when($options === 0, fn (Expectation $ex): Expectation => $ex->toBe(0)), ); })->with('method_params'); @@ -101,23 +103,23 @@ array $headers, int $options, ): void { - $result = Response::ok($data, $headers, $options); + $jsonResponse = Response::ok($data, $headers, $options); - expect($result) + expect($jsonResponse) ->toBeInstanceOf(JsonResponse::class) - ->and($result->getData(true)) - ->when(!is_null($data), fn (Expectation $ex) => $ex->toBe($data)) - ->when(is_null($data), fn (Expectation $ex) => $ex->toBeArray()->toBeEmpty()) - ->and($result->getStatusCode()) + ->and($jsonResponse->getData(true)) + ->when(!is_null($data), fn (Expectation $ex): Expectation => $ex->toBe($data)) + ->when(is_null($data), fn (Expectation $ex): Expectation => $ex->toBeArray()->toBeEmpty()) + ->and($jsonResponse->getStatusCode()) ->toBe(200) - ->and($result->headers->get('foo')) - ->when([] !== $headers, fn (Expectation $ex) => $ex->toBe('bar')) + ->and($jsonResponse->headers->get('foo')) + ->when($headers !== [], fn (Expectation $ex): Expectation => $ex->toBe('bar')) ->when( - [] === $headers, - fn (Expectation $ex) => $ex->toBeEmpty() - ->and($result->getEncodingOptions()) - ->when(0 !== $options, fn (Expectation $ex) => $ex->toBe(1)) - ->when(0 === $options, fn (Expectation $ex) => $ex->toBe(0)), + $headers === [], + fn (Expectation $ex): Expectation => $ex->toBeEmpty() + ->and($jsonResponse->getEncodingOptions()) + ->when($options !== 0, fn (Expectation $ex): Expectation => $ex->toBe(1)) + ->when($options === 0, fn (Expectation $ex): Expectation => $ex->toBe(0)), ); })->with('method_params'); @@ -125,22 +127,22 @@ array $headers, int $options, ): void { - $result = Response::noContent($headers, $options); + $jsonResponse = Response::noContent($headers, $options); - expect($result) + expect($jsonResponse) ->toBeInstanceOf(JsonResponse::class) - ->and($result->getData(true)) + ->and($jsonResponse->getData(true)) ->toBeEmpty() - ->and($result->getStatusCode()) + ->and($jsonResponse->getStatusCode()) ->toBe(204) - ->and($result->headers->get('foo')) - ->when([] !== $headers, fn (Expectation $ex) => $ex->toBe('bar')) + ->and($jsonResponse->headers->get('foo')) + ->when($headers !== [], fn (Expectation $ex): Expectation => $ex->toBe('bar')) ->when( - [] === $headers, - fn (Expectation $ex) => $ex->toBeEmpty() - ->and($result->getEncodingOptions()) - ->when(0 !== $options, fn (Expectation $ex) => $ex->toBe(1)) - ->when(0 === $options, fn (Expectation $ex) => $ex->toBe(0)), + $headers === [], + fn (Expectation $ex): Expectation => $ex->toBeEmpty() + ->and($jsonResponse->getEncodingOptions()) + ->when($options !== 0, fn (Expectation $ex): Expectation => $ex->toBe(1)) + ->when($options === 0, fn (Expectation $ex): Expectation => $ex->toBe(0)), ); })->with([ [[], 0], diff --git a/tests/Unit/Support/HashidsManagerDecoratorTest.php b/tests/Unit/Support/HashidsManagerDecoratorTest.php index 57120f179..bf549f93c 100644 --- a/tests/Unit/Support/HashidsManagerDecoratorTest.php +++ b/tests/Unit/Support/HashidsManagerDecoratorTest.php @@ -1,5 +1,7 @@ toBeTrue(); + expect(in_array(ForwardsCalls::class, class_uses(HashidsManagerDecorator::class), true))->toBeTrue(); }); it('should use the Macroable trait', function (): void { - expect(in_array(Macroable::class, class_uses(HashidsManagerDecorator::class)))->toBeTrue(); + expect(in_array(Macroable::class, class_uses(HashidsManagerDecorator::class), true))->toBeTrue(); }); - it('can decode or null', function (string $hashId, int|array|null $expectation): void { + it('can decode or null', function (string $hashId, null|int|array $expectation): void { $sut = new HashidsManagerDecorator(new HashidsManager(config(), app('hashids.factory'))); $result = $sut->decode($hashId); expect($result)->toBe($expectation); })->with([ - [fn () => hashids()->encodeOrFail(10), 10], - [fn () => hashids()->encodeOrFail(10, 13, 2), [10, 13, 2]], + [fn (): string => hashids()->encodeOrFail(10), 10], + [fn (): string => hashids()->encodeOrFail(10, 13, 2), [10, 13, 2]], ['invalid', null], ]); - it('can decode or throw an exception', function (string $hashId, int|array|null $expectation): void { + it('can decode or throw an exception', function (string $hashId, null|int|array $expectation): void { $sut = new HashidsManagerDecorator(new HashidsManager(config(), app('hashids.factory'))); - expect(static function () use ($sut, $hashId) { + expect(static function () use ($sut, $hashId): void { $sut->decodeOrFail($hashId); })->when( is_null($expectation), - fn (Expectation $ex) => $ex + fn (Expectation $ex): Expectation => $ex ->toThrow(InvalidArgumentException::class), - )->unless(is_null($expectation), fn (Expectation $ex) => $ex + )->unless(is_null($expectation), fn (Expectation $ex): Expectation => $ex ->toBe($ex->value)); })->with([ - [fn () => hashids()->encodeOrFail(10), 10], - [fn () => hashids()->encodeOrFail(10, 13, 2), [10, 13, 2]], + [fn (): string => hashids()->encodeOrFail(10), 10], + [fn (): string => hashids()->encodeOrFail(10, 13, 2), [10, 13, 2]], ['invalid', null], ]); @@ -63,36 +65,36 @@ expect($result)->toBe([1, 2, 3]); }); - it('can encode or null', function (array $numbers, string|null $expectation): void { + it('can encode or null', function (array $numbers, null|string $expectation): void { $sut = new HashidsManagerDecorator(new HashidsManager(config(), app('hashids.factory'))); $result = $sut->encode(...$numbers); expect($result)->toBe($expectation); })->with([ - [[10], fn () => hashids()->encodeOrFail(10)], - [['15'], fn () => hashids()->encodeOrFail(15)], - [[10, 20], fn () => hashids()->encodeOrFail(10, 20)], - [[10, '20'], fn () => hashids()->encodeOrFail(10, 20)], + [[10], fn (): string => hashids()->encodeOrFail(10)], + [['15'], fn (): string => hashids()->encodeOrFail(15)], + [[10, 20], fn (): string => hashids()->encodeOrFail(10, 20)], + [[10, '20'], fn (): string => hashids()->encodeOrFail(10, 20)], [[], null], ]); - it('can encode or throw', function (array $numbers, string|null $expectation): void { + it('can encode or throw', function (array $numbers, null|string $expectation): void { $sut = new HashidsManagerDecorator(new HashidsManager(config(), app('hashids.factory'))); - expect(static function () use ($sut, $numbers) { + expect(static function () use ($sut, $numbers): void { $sut->encodeOrFail(...$numbers); })->when( is_null($expectation), - fn (Expectation $ex) => $ex + fn (Expectation $ex): Expectation => $ex ->toThrow(InvalidArgumentException::class), )->when( !is_null($expectation), - fn (Expectation $ex) => $ex + fn (Expectation $ex): Expectation => $ex ->toBe($ex->value), ); })->with([ - [[10], fn () => hashids()->encodeOrFail(10)], + [[10], fn (): string => hashids()->encodeOrFail(10)], [[], null], ]); @@ -105,7 +107,7 @@ it('prioritize macro methods if it exists when delegating method calls', function (): void { $sut = new HashidsManagerDecorator(new HashidsManager(config(), app('hashids.factory'))); - HashidsManagerDecorator::macro('getDefaultConnection', fn () => 'something'); + HashidsManagerDecorator::macro('getDefaultConnection', fn (): string => 'something'); expect($sut->getDefaultConnection())->toBe('something'); }); diff --git a/tests/Unit/Support/SanitizerTest.php b/tests/Unit/Support/SanitizerTest.php index a98cbea8e..a12e70949 100644 --- a/tests/Unit/Support/SanitizerTest.php +++ b/tests/Unit/Support/SanitizerTest.php @@ -1,11 +1,14 @@ merge([ ...$requestData, 'something' => 'that should be removed', @@ -58,7 +61,7 @@ 'dot notation with default value' => [ [], ['data.name' => 'Gandalf'], - ['data' => ['name' => 'Gandalf']], + ['data' => ['name' => 'Gandalf']], ], ]); })->covers(Sanitizer::class); diff --git a/tests/UnitTestCase.php b/tests/UnitTestCase.php index 126f50de5..b27cf7ec2 100644 --- a/tests/UnitTestCase.php +++ b/tests/UnitTestCase.php @@ -1,5 +1,7 @@ - */ + /** @var class-string */ protected $model = User::class; public function definition(): array { return [ - 'name' => $this->faker->name, - 'email' => $this->faker->unique()->safeEmail, - 'password' => $this->faker->password, + 'name' => fake()->name, + 'email' => fake()->unique()->safeEmail, + 'password' => fake()->password, ]; } public function withParent(): static { - return $this->state(fn (array $attributes): array => [ + return $this->state(static fn (array $attributes): array => [ 'parent_id' => static::new()->createOne()->id, ]); } public function withChildren(int $count = 1): static { - return $this->afterCreating(function (User $user) use ($count): void { + return $this->afterCreating(static function (User $user) use ($count): void { static::new()->count($count)->create([ 'parent_id' => $user->id, ]); diff --git a/workbench/app/Containers/Identity/User/Data/Migrations/0001_01_01_000000_testbench_create_users_table.php b/workbench/app/Containers/Identity/User/Data/Migrations/0001_01_01_000000_testbench_create_users_table.php index 56a78a136..c15b74fba 100644 --- a/workbench/app/Containers/Identity/User/Data/Migrations/0001_01_01_000000_testbench_create_users_table.php +++ b/workbench/app/Containers/Identity/User/Data/Migrations/0001_01_01_000000_testbench_create_users_table.php @@ -1,10 +1,12 @@ 'datetime', - ]; - public function parent(): BelongsTo { return $this->belongsTo(self::class, 'parent_id'); @@ -49,4 +47,11 @@ public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); } + + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + ]; + } } diff --git a/workbench/app/Containers/Identity/User/UI/API/Transformers/UserTransformer.php b/workbench/app/Containers/Identity/User/UI/API/Transformers/UserTransformer.php index da624caed..fb2eb783d 100644 --- a/workbench/app/Containers/Identity/User/UI/API/Transformers/UserTransformer.php +++ b/workbench/app/Containers/Identity/User/UI/API/Transformers/UserTransformer.php @@ -1,5 +1,7 @@ $user->getResourceKey(), - 'id' => $user->getHashedKey(), - 'name' => $user->name, - 'email' => $user->email, + 'type' => $user->getResourceKey(), + 'id' => $user->getHashedKey(), + 'name' => $user->name, + 'email' => $user->email, 'created_at' => $user->created_at, 'updated_at' => $user->updated_at, ]; @@ -51,6 +53,6 @@ public function includeBooks(User $user): Collection public function includeComments(User $user): Collection { - return $this->collection($user->comments, fn (Comment $comment) => $comment->toArray()); + return $this->collection($user->comments, static fn (Comment $comment) => $comment->toArray()); } } diff --git a/workbench/app/Containers/MySection/Author/Actions/SimpleAction.php b/workbench/app/Containers/MySection/Author/Actions/SimpleAction.php index b366efdf7..1c21afefb 100644 --- a/workbench/app/Containers/MySection/Author/Actions/SimpleAction.php +++ b/workbench/app/Containers/MySection/Author/Actions/SimpleAction.php @@ -1,5 +1,7 @@

This is a fake mail view for testing purposes.

+ 'List all Authors'); diff --git a/workbench/app/Containers/MySection/Author/UI/WEB/Routes/ListAuthors.php b/workbench/app/Containers/MySection/Author/UI/WEB/Routes/ListAuthors.php index 403486b53..fb1b4f033 100644 --- a/workbench/app/Containers/MySection/Author/UI/WEB/Routes/ListAuthors.php +++ b/workbench/app/Containers/MySection/Author/UI/WEB/Routes/ListAuthors.php @@ -1,5 +1,7 @@ 'List all Authors'); diff --git a/workbench/app/Containers/MySection/Book/Actions/CreateBookAction.php b/workbench/app/Containers/MySection/Book/Actions/CreateBookAction.php index 1515dad31..2d1b85ddc 100644 --- a/workbench/app/Containers/MySection/Book/Actions/CreateBookAction.php +++ b/workbench/app/Containers/MySection/Book/Actions/CreateBookAction.php @@ -1,5 +1,7 @@ - */ + /** @var class-string */ protected $model = Book::class; public function definition(): array { return [ - 'title' => fake()->sentence, + 'title' => fake()->sentence, 'author_id' => User::factory(), ]; } diff --git a/workbench/app/Containers/MySection/Book/Data/Migrations/2024_12_29_144159_create_books_table.php b/workbench/app/Containers/MySection/Book/Data/Migrations/2024_12_29_144159_create_books_table.php index bfba11516..7d5da9303 100644 --- a/workbench/app/Containers/MySection/Book/Data/Migrations/2024_12_29_144159_create_books_table.php +++ b/workbench/app/Containers/MySection/Book/Data/Migrations/2024_12_29_144159_create_books_table.php @@ -1,10 +1,12 @@ 'forbidden', ]; diff --git a/workbench/app/Containers/MySection/Book/Languages/fa/errors.php b/workbench/app/Containers/MySection/Book/Languages/fa/errors.php index 63bb48986..86ebe1d8b 100644 --- a/workbench/app/Containers/MySection/Book/Languages/fa/errors.php +++ b/workbench/app/Containers/MySection/Book/Languages/fa/errors.php @@ -1,5 +1,7 @@ 'ممنوع', ]; diff --git a/workbench/app/Containers/MySection/Book/Listeners/BookCreatedListener.php b/workbench/app/Containers/MySection/Book/Listeners/BookCreatedListener.php index 61656794c..7d7d180ac 100644 --- a/workbench/app/Containers/MySection/Book/Listeners/BookCreatedListener.php +++ b/workbench/app/Containers/MySection/Book/Listeners/BookCreatedListener.php @@ -1,5 +1,7 @@ afterResolving(Kernel::class, function (Kernel $kernel): void { + app()->afterResolving(Kernel::class, static function (Kernel $kernel): void { $kernel->pushMiddleware(BeforeMiddleware::class); }); } diff --git a/workbench/app/Containers/MySection/Book/Providers/EventServiceProvider.php b/workbench/app/Containers/MySection/Book/Providers/EventServiceProvider.php index 54a54e4aa..e62e8d8eb 100644 --- a/workbench/app/Containers/MySection/Book/Providers/EventServiceProvider.php +++ b/workbench/app/Containers/MySection/Book/Providers/EventServiceProvider.php @@ -1,5 +1,7 @@ [ - 'id' => $request->input('id'), - 'id-default' => $request->input('id', 100), - 'title' => $request->input('title'), - 'hashed_id' => $request->input('hashed_id'), - 'nested.id' => $request->input('nested.id'), - 'nested.with-default' => $request->input('nested.with', 200), - 'author_id' => $request->input('author_id'), - 'authors' => $request->input('authors'), - 'authors.*.id' => $request->input('authors.*.id'), + 'id' => $request->input('id'), + 'id-default' => $request->input('id', 100), + 'title' => $request->input('title'), + 'hashed_id' => $request->input('hashed_id'), + 'nested.id' => $request->input('nested.id'), + 'nested.with-default' => $request->input('nested.with', 200), + 'author_id' => $request->input('author_id'), + 'authors' => $request->input('authors'), + 'authors.*.id' => $request->input('authors.*.id'), 'authors.*.with-default' => $request->input('authors.*.with', 150), - 'ids' => $request->input('ids'), - 'with-default' => $request->input('with', [1, 2, 3]), - 'none_existing' => $request->input('none_existing'), - 'optional_id' => $request->input('optional_id'), + 'ids' => $request->input('ids'), + 'with-default' => $request->input('with', [1, 2, 3]), + 'none_existing' => $request->input('none_existing'), + 'optional_id' => $request->input('optional_id'), ], 'all(val)' => [ - 'id' => $request->all('id'), - 'title' => $request->all('title'), - 'nested.id' => $request->all('nested.id'), - 'nested.ids' => $request->all('nested.ids'), - 'author_id' => $request->all('author_id'), + 'id' => $request->all('id'), + 'title' => $request->all('title'), + 'nested.id' => $request->all('nested.id'), + 'nested.ids' => $request->all('nested.ids'), + 'author_id' => $request->all('author_id'), 'none_existing' => $request->all('none_existing'), - 'optional_id' => $request->all('optional_id'), + 'optional_id' => $request->all('optional_id'), ], 'route(val)' => [ - 'id' => $request->route('id'), + 'id' => $request->route('id'), 'none_existing' => $request->route('none_existing'), ], 'request->val' => [ - 'id' => $request->id, - 'title' => $request->title, + 'id' => $request->id, + 'title' => $request->title, 'none_existing' => $request->none_existing, - 'optional_id' => $request->optional_id, + 'optional_id' => $request->optional_id, ], - 'input()' => $request->input(), - 'all()' => $request->all(), - 'validated' => $request->validated(), + 'input()' => $request->input(), + 'all()' => $request->all(), + 'validated' => $request->validated(), 'route()::class' => $request->route()::class, ]); } diff --git a/workbench/app/Containers/MySection/Book/UI/API/Requests/CreateBookRequest.php b/workbench/app/Containers/MySection/Book/UI/API/Requests/CreateBookRequest.php index 33cb358e0..f25f10942 100644 --- a/workbench/app/Containers/MySection/Book/UI/API/Requests/CreateBookRequest.php +++ b/workbench/app/Containers/MySection/Book/UI/API/Requests/CreateBookRequest.php @@ -1,5 +1,7 @@ 'required', - 'author_id' => 'required', - 'ids.*' => ['required', Rule::when($hashIdEnabled, 'integer', 'string')], + 'title' => 'required', + 'author_id' => 'required', + 'ids.*' => ['required', Rule::when($hashIdEnabled, 'integer', 'string')], 'authors.*.id' => ['required', Rule::when($hashIdEnabled, 'integer', 'string')], - 'nested.id' => ['required', Rule::when($hashIdEnabled, 'integer', 'string')], - 'nested.ids' => 'array', + 'nested.id' => ['required', Rule::when($hashIdEnabled, 'integer', 'string')], + 'nested.ids' => 'array', 'nested.ids.*' => Rule::when($hashIdEnabled, 'integer', 'string'), ]; } diff --git a/workbench/app/Containers/MySection/Book/UI/API/Routes/CreateBook.v1.private.php b/workbench/app/Containers/MySection/Book/UI/API/Routes/CreateBook.v1.private.php index ac456e69d..c33e6ec1c 100644 --- a/workbench/app/Containers/MySection/Book/UI/API/Routes/CreateBook.v1.private.php +++ b/workbench/app/Containers/MySection/Book/UI/API/Routes/CreateBook.v1.private.php @@ -1,5 +1,7 @@ 'Get All Books'); diff --git a/workbench/app/Containers/MySection/Book/UI/API/Routes/UpdateBook.v1.private.php b/workbench/app/Containers/MySection/Book/UI/API/Routes/UpdateBook.v1.private.php index 9636e3a27..1291c20e4 100644 --- a/workbench/app/Containers/MySection/Book/UI/API/Routes/UpdateBook.v1.private.php +++ b/workbench/app/Containers/MySection/Book/UI/API/Routes/UpdateBook.v1.private.php @@ -1,5 +1,7 @@ $book->getResourceKey(), - 'id' => $book->getHashedKey(), - 'title' => $book->title, - 'author' => $book->author?->name, + 'type' => $book->getResourceKey(), + 'id' => $book->getHashedKey(), + 'title' => $book->title, + 'author' => $book->author?->name, 'created_at' => $book->created_at, 'updated_at' => $book->updated_at, ]; diff --git a/workbench/app/Containers/MySection/Book/UI/CLI/Commands/ContainerTestCommand.php b/workbench/app/Containers/MySection/Book/UI/CLI/Commands/ContainerTestCommand.php index 313d9dddb..c6b1aeb0c 100644 --- a/workbench/app/Containers/MySection/Book/UI/CLI/Commands/ContainerTestCommand.php +++ b/workbench/app/Containers/MySection/Book/UI/CLI/Commands/ContainerTestCommand.php @@ -1,5 +1,7 @@ 'Get All Books'); diff --git a/workbench/app/Containers/MySection/Book/UI/WEB/Views/book-me.blade.php b/workbench/app/Containers/MySection/Book/UI/WEB/Views/book-me.blade.php index bf6d11aa3..33fc011a6 100644 --- a/workbench/app/Containers/MySection/Book/UI/WEB/Views/book-me.blade.php +++ b/workbench/app/Containers/MySection/Book/UI/WEB/Views/book-me.blade.php @@ -1,3 +1,9 @@ +

This is a fake view

+ $this->faker->text, + 'content' => fake()->text, ]; } } diff --git a/workbench/app/Containers/SocialInteraction/Comment/Data/Migrations/2025_01_25_224724_create_comments_table.php b/workbench/app/Containers/SocialInteraction/Comment/Data/Migrations/2025_01_25_224724_create_comments_table.php index 5de9121a6..cd0a4a577 100644 --- a/workbench/app/Containers/SocialInteraction/Comment/Data/Migrations/2025_01_25_224724_create_comments_table.php +++ b/workbench/app/Containers/SocialInteraction/Comment/Data/Migrations/2025_01_25_224724_create_comments_table.php @@ -1,10 +1,12 @@ Apiato\Http\Response::class, + 'fractal_class' => Response::class, 'auto_includes' => [ /* diff --git a/workbench/app/Ship/Configs/repository.php b/workbench/app/Ship/Configs/repository.php index 71fa24bdc..d2a7f016a 100644 --- a/workbench/app/Ship/Configs/repository.php +++ b/workbench/app/Ship/Configs/repository.php @@ -1,5 +1,9 @@ [ 'include' => 'include', ], - 'serializer' => League\Fractal\Serializer\DataArraySerializer::class, + 'serializer' => DataArraySerializer::class, ], /* @@ -137,7 +141,7 @@ | 'except' =>['find'], */ 'allowed' => [ - 'only' => null, + 'only' => null, 'except' => null, ], ], @@ -210,14 +214,14 @@ | */ 'params' => [ - 'search' => 'search', + 'search' => 'search', 'searchFields' => 'searchFields', // 'filter' => 'filter', - 'orderBy' => 'orderBy', + 'orderBy' => 'orderBy', 'sortedBy' => 'sortedBy', // 'with' => 'with', 'searchJoin' => 'searchJoin', - 'withCount' => 'withCount', + 'withCount' => 'withCount', ], ], /* @@ -227,19 +231,19 @@ | */ 'generator' => [ - 'basePath' => app()->path(), - 'rootNamespace' => 'App\\', + 'basePath' => app()->path(), + 'rootNamespace' => 'App\\', 'stubsOverridePath' => app()->path(), - 'paths' => [ - 'models' => 'Entities', + 'paths' => [ + 'models' => 'Entities', 'repositories' => 'Repositories', - 'interfaces' => 'Repositories', + 'interfaces' => 'Repositories', 'transformers' => 'Transformers', - 'presenters' => 'Presenters', - 'validators' => 'Validators', - 'controllers' => 'Http/Controllers', - 'provider' => 'RepositoryServiceProvider', - 'criteria' => 'Criteria', + 'presenters' => 'Presenters', + 'validators' => 'Validators', + 'controllers' => 'Http/Controllers', + 'provider' => 'RepositoryServiceProvider', + 'criteria' => 'Criteria', ], ], ]; diff --git a/workbench/app/Ship/Exceptions/CreateResourceFailed.php b/workbench/app/Ship/Exceptions/CreateResourceFailed.php index d57d4d890..f0d161061 100755 --- a/workbench/app/Ship/Exceptions/CreateResourceFailed.php +++ b/workbench/app/Ship/Exceptions/CreateResourceFailed.php @@ -1,5 +1,7 @@ 'forbidden', ]; diff --git a/workbench/app/Ship/Languages/fa/errors.php b/workbench/app/Ship/Languages/fa/errors.php index 63bb48986..86ebe1d8b 100644 --- a/workbench/app/Ship/Languages/fa/errors.php +++ b/workbench/app/Ship/Languages/fa/errors.php @@ -1,5 +1,7 @@ 'ممنوع', ]; diff --git a/workbench/app/Ship/Mails/Templates/welcome-cheers.blade.php b/workbench/app/Ship/Mails/Templates/welcome-cheers.blade.php index 1b6be34a7..0d320d246 100644 --- a/workbench/app/Ship/Mails/Templates/welcome-cheers.blade.php +++ b/workbench/app/Ship/Mails/Templates/welcome-cheers.blade.php @@ -1,3 +1,9 @@ +

This is a fake email view

+

This is a fake view

+webRoutes(), then: static fn () => $apiato->registerApiRoutes(), ) - ->withMiddleware(function (Middleware $middleware): void { + ->withMiddleware(static function (Middleware $middleware): void { $middleware->api([ ValidateJsonContent::class, ProcessETag::class, From 174d19e170b06e8027b21c6caa43f8d6f37db65d Mon Sep 17 00:00:00 2001 From: Dmytro Vovk Date: Mon, 9 Jun 2025 16:09:15 +0200 Subject: [PATCH 2/3] fix: update placeholders and PHP unit versions in configuration files for compatibility with version 13.x; --- .github/ISSUE_TEMPLATE/Bug_report.yml | 2 +- .github/workflows/matrix_includes.json | 4 ++-- .github/workflows/php-cs-fixer.yaml | 4 ++-- .github/workflows/phpcs.yaml | 4 ++-- .github/workflows/phpmd.yaml | 4 ++-- .github/workflows/phpstan.yaml | 33 +++++++------------------- .github/workflows/rector.yaml | 4 ++-- .github/workflows/tests.yaml | 6 +++-- 8 files changed, 23 insertions(+), 38 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index 694259057..6ae4bf5bd 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -8,7 +8,7 @@ body: attributes: label: Apiato Core Version description: Provide the Apiato Core version that you are using. [Please ensure it is still supported.](https://apiato.io/docs/prologue/release-notes#support-policy) - placeholder: 8.x + placeholder: 13.x validations: required: true - type: input diff --git a/.github/workflows/matrix_includes.json b/.github/workflows/matrix_includes.json index 41a1b187a..3e16f9fdc 100644 --- a/.github/workflows/matrix_includes.json +++ b/.github/workflows/matrix_includes.json @@ -15,7 +15,7 @@ "runs_on": "ubuntu-latest", "runOn":"master", "php_version":"8.4", - "php_unit_version": "phpunit:11.*" + "php_unit_version": "phpunit:12.*" }, { "runs_on": "ubuntu-latest", @@ -33,6 +33,6 @@ "runs_on": "ubuntu-latest", "runOn":"13.x", "php_version":"8.4", - "php_unit_version": "phpunit:11.*" + "php_unit_version": "phpunit:12.*" } ] diff --git a/.github/workflows/php-cs-fixer.yaml b/.github/workflows/php-cs-fixer.yaml index bcbc3479c..71c419cdb 100644 --- a/.github/workflows/php-cs-fixer.yaml +++ b/.github/workflows/php-cs-fixer.yaml @@ -36,9 +36,9 @@ jobs: uses: actions/cache@v4 with: path: .php-cs-fixer.cache - key: ${{ runner.os }}-php-cs-fixer-${{ github.sha }} + key: ${{ runner.OS }}-${{ github.repository }}-php-cs-fixer-${{ github.sha }} restore-keys: | - ${{ runner.os }}-php-cs-fixer- + ${{ runner.OS }}-${{ github.repository }}-php-cs-fixer- - name: Install Composer dependencies uses: ramsey/composer-install@v3 diff --git a/.github/workflows/phpcs.yaml b/.github/workflows/phpcs.yaml index 097915c9e..67e56eb26 100644 --- a/.github/workflows/phpcs.yaml +++ b/.github/workflows/phpcs.yaml @@ -36,9 +36,9 @@ jobs: uses: actions/cache@v4 with: path: .phpcs-cache - key: ${{ runner.os }}-phpcs-${{ github.sha }} + key: ${{ runner.OS }}-${{ github.repository }}-phpcs-${{ github.sha }} restore-keys: | - ${{ runner.os }}-phpcs- + ${{ runner.OS }}-${{ github.repository }}-phpcs- - name: Install Composer dependencies uses: ramsey/composer-install@v3 diff --git a/.github/workflows/phpmd.yaml b/.github/workflows/phpmd.yaml index 0a483fba9..f47430256 100644 --- a/.github/workflows/phpmd.yaml +++ b/.github/workflows/phpmd.yaml @@ -36,9 +36,9 @@ jobs: uses: actions/cache@v4 with: path: .phpmd.result-cache.php - key: ${{ runner.os }}-phpmd-${{ github.sha }} + key: ${{ runner.OS }}-${{ github.repository }}-phpmd-${{ github.sha }} restore-keys: | - ${{ runner.os }}-phpmd- + ${{ runner.OS }}-${{ github.repository }}-phpmd- - name: Install Composer dependencies uses: ramsey/composer-install@v3 diff --git a/.github/workflows/phpstan.yaml b/.github/workflows/phpstan.yaml index 33fc0dce4..9003cf799 100644 --- a/.github/workflows/phpstan.yaml +++ b/.github/workflows/phpstan.yaml @@ -22,38 +22,21 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite - coverage: none - - name: Setup Composer token run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + - name: Copy environment file for Composer + run: cp .env.ci .env + - name: Install Composer dependencies uses: ramsey/composer-install@v3 with: composer-options: --prefer-dist - - name: Prepare PHPStan cache directory - run: mkdir -p ./tmp/phpstan - - - name: Restore PHPStan result cache - uses: actions/cache/restore@v4 - with: - path: ./tmp/phpstan - key: ${{ runner.os }}-phpstan-result-cache-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-phpstan-result-cache- - - name: Run PHPStan - run: vendor/bin/phpstan analyse --memory-limit=512M --ansi --error-format=github - - - name: Save PHPStan result cache - uses: actions/cache/save@v4 - if: ${{ !cancelled() }} + uses: php-actions/phpstan@v3 with: - path: ./tmp/phpstan - key: phpstan-result-cache-${{ github.sha }} + configuration: phpstan.neon + path: app/ config/ tests/ database/ + version: 'composer' + php_version: '8.3' diff --git a/.github/workflows/rector.yaml b/.github/workflows/rector.yaml index fd1ff1fe2..d29f03da5 100644 --- a/.github/workflows/rector.yaml +++ b/.github/workflows/rector.yaml @@ -39,9 +39,9 @@ jobs: uses: actions/cache@v4 with: path: ./tmp/rector - key: ${{ runner.os }}-rector-${{ github.sha }} + key: ${{ runner.OS }}-${{ github.repository }}-rector-${{ github.sha }} restore-keys: | - ${{ runner.os }}-rector- + ${{ runner.OS }}-${{ github.repository }}-rector- - name: Install Composer dependencies uses: ramsey/composer-install@v3 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 16a93cc74..34319d256 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -51,9 +51,9 @@ jobs: uses: actions/cache@v4 with: path: .phpunit.result.cache - key: ${{ runner.os }}-php-${{ matrix.php }}-phpunit-${{ github.sha }} + key: ${{ runner.OS }}-${{ github.repository }}-php-${{ matrix.php }}-phpunit-${{ github.sha }} restore-keys: | - ${{ runner.os }}-php-${{ matrix.php }}-phpunit- + ${{ runner.OS }}-${{ github.repository }}-php-${{ matrix.php }}-phpunit- - name: Install Composer dependencies uses: ramsey/composer-install@v3 @@ -66,6 +66,8 @@ jobs: - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: + slug: apiato/core + files: coverage.xml fail_ci_if_error: true verbose: true env: From 7bc61cdcce9ebb7f15fac212e122bea917534005 Mon Sep 17 00:00:00 2001 From: Dmytro Vovk Date: Mon, 9 Jun 2025 17:17:28 +0200 Subject: [PATCH 3/3] ref: remove unnecessary environment file copy step in CI configuration; --- .github/workflows/phpstan.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/phpstan.yaml b/.github/workflows/phpstan.yaml index 9003cf799..ec15c8ddd 100644 --- a/.github/workflows/phpstan.yaml +++ b/.github/workflows/phpstan.yaml @@ -25,9 +25,6 @@ jobs: - name: Setup Composer token run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} - - name: Copy environment file for Composer - run: cp .env.ci .env - - name: Install Composer dependencies uses: ramsey/composer-install@v3 with: