From 54303282d7a16bf5df90edff4fb783b421aad38e Mon Sep 17 00:00:00 2001 From: Funky Waddle Date: Tue, 6 Jan 2026 11:02:05 -0600 Subject: [PATCH] Too many things --- .env.example | 12 + .php-cs-fixer.php | 19 + CONTRIBUTING.md | 24 ++ Dockerfile | 22 ++ MILESTONES.md | 355 +++++++++--------- README.md | 61 ++- bin/phred | 2 +- codeception.yml | 12 + composer.json | 5 + docker/nginx.conf | 19 + phpstan-baseline.neon | 76 ++++ phpstan.neon | 16 + phpstan.neon.dist | 14 +- src/Cache/FileCache.php | 104 +++++ src/Console/Command.php | 2 +- .../Contracts/ApiResponseFactoryInterface.php | 3 +- src/Http/Controllers/HealthController.php | 45 ++- .../Controllers/OpenApiJsonController.php | 31 ++ src/Http/Controllers/OpenApiUiController.php | 43 +++ src/Http/JsonApi/SchemaProviderInterface.php | 13 + src/Http/Kernel.php | 147 ++++++-- .../Cache/ResponseCacheMiddleware.php | 82 ++++ src/Http/Middleware/CompressionMiddleware.php | 72 ++++ .../ContentNegotiationMiddleware.php | 21 +- src/Http/Middleware/DispatchMiddleware.php | 40 +- .../JsonApi/JsonApiQueryMiddleware.php | 44 +++ .../Middleware/MiddlewareGroupMiddleware.php | 69 ++++ src/Http/Middleware/RoutingMiddleware.php | 10 +- .../Middleware/TrustedProxiesMiddleware.php | 45 +++ .../UrlExtensionNegotiationMiddleware.php | 4 +- .../DelegatingApiResponseFactory.php | 6 +- src/Http/Responses/JsonApiResponseFactory.php | 10 +- src/Http/Responses/RestResponseFactory.php | 10 +- src/Http/Responses/XmlResponseFactory.php | 8 +- src/Http/Router.php | 63 +++- src/Http/Routing/RouteGroups.php | 5 +- src/Http/Routing/RouteRegistry.php | 14 + src/Http/Support/ConditionalRequestTrait.php | 27 ++ src/OpenApi/Spec.php | 20 + src/Providers/Core/CacheServiceProvider.php | 28 ++ src/Providers/Core/OpenApiServiceProvider.php | 31 ++ src/Support/Http/CircuitBreakerMiddleware.php | 2 +- .../PhpStan/Rules/InvokableControllerRule.php | 92 +++++ src/Support/ProviderRepository.php | 31 +- src/Testing/CodeceptionRunner.php | 9 +- src/Testing/TestCase.php | 58 +++ src/Testing/TestResponse.php | 64 ++++ src/commands/create_controller.php | 7 +- src/commands/create_migration.php | 2 +- src/commands/create_model.php | 5 +- src/commands/create_module.php | 29 +- src/commands/create_seed.php | 2 +- src/commands/create_test.php | 5 +- src/commands/create_view.php | 5 +- src/commands/docs.php | 41 ++ src/commands/generate_openapi.php | 60 +++ src/commands/module_list.php | 70 ++++ src/commands/module_prune.php | 82 ++++ src/commands/module_register.php | 181 +++++++++ src/commands/module_sync_ns.php | 79 ++++ src/commands/route_cache.php | 48 +++ src/commands/route_clear.php | 25 ++ src/commands/route_list.php | 95 +++++ src/stubs/controller.stub | 8 +- tests/CreateModuleCommandTest.php | 14 +- tests/Feature/CompressionMiddlewareTest.php | 48 +++ tests/Feature/FeatureTestCaseTest.php | 48 +++ tests/Feature/M13FeaturesTest.php | 60 +++ tests/Feature/M15CachingTest.php | 42 +++ tests/Feature/ModuleSyncNsTest.php | 38 ++ tests/Feature/NewOpportunityRadarTest.php | 2 +- tests/RouterGroupTest.php | 2 +- tests/Support/Data/.gitkeep | 0 tests/Support/_generated/.gitignore | 2 + tests/Unit/FakerDemoTest.php | 22 ++ 75 files changed, 2589 insertions(+), 323 deletions(-) create mode 100644 .php-cs-fixer.php create mode 100644 CONTRIBUTING.md create mode 100644 Dockerfile create mode 100644 codeception.yml create mode 100644 docker/nginx.conf create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon create mode 100644 src/Cache/FileCache.php create mode 100644 src/Http/Controllers/OpenApiJsonController.php create mode 100644 src/Http/Controllers/OpenApiUiController.php create mode 100644 src/Http/JsonApi/SchemaProviderInterface.php create mode 100644 src/Http/Middleware/Cache/ResponseCacheMiddleware.php create mode 100644 src/Http/Middleware/CompressionMiddleware.php create mode 100644 src/Http/Middleware/JsonApi/JsonApiQueryMiddleware.php create mode 100644 src/Http/Middleware/MiddlewareGroupMiddleware.php create mode 100644 src/Http/Middleware/TrustedProxiesMiddleware.php create mode 100644 src/Http/Support/ConditionalRequestTrait.php create mode 100644 src/OpenApi/Spec.php create mode 100644 src/Providers/Core/CacheServiceProvider.php create mode 100644 src/Providers/Core/OpenApiServiceProvider.php create mode 100644 src/Support/PhpStan/Rules/InvokableControllerRule.php create mode 100644 src/Testing/TestCase.php create mode 100644 src/Testing/TestResponse.php create mode 100644 src/commands/docs.php create mode 100644 src/commands/generate_openapi.php create mode 100644 src/commands/module_list.php create mode 100644 src/commands/module_prune.php create mode 100644 src/commands/module_register.php create mode 100644 src/commands/module_sync_ns.php create mode 100644 src/commands/route_cache.php create mode 100644 src/commands/route_clear.php create mode 100644 src/commands/route_list.php create mode 100644 tests/Feature/CompressionMiddlewareTest.php create mode 100644 tests/Feature/FeatureTestCaseTest.php create mode 100644 tests/Feature/M13FeaturesTest.php create mode 100644 tests/Feature/M15CachingTest.php create mode 100644 tests/Feature/ModuleSyncNsTest.php create mode 100644 tests/Support/Data/.gitkeep create mode 100644 tests/Support/_generated/.gitignore create mode 100644 tests/Unit/FakerDemoTest.php diff --git a/.env.example b/.env.example index 779f74e..80383bf 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,10 @@ APP_NAME=Phred App APP_ENV=local APP_DEBUG=true APP_TIMEZONE=UTC +APP_URL=http://localhost + API_FORMAT=rest +API_PROBLEM_DETAILS=true DB_DRIVER=sqlite DB_DATABASE=database/database.sqlite @@ -15,3 +18,12 @@ ORM_DRIVER=pairity TEMPLATE_DRIVER=eyrie FLAGS_DRIVER=flagpole TEST_RUNNER=codeception +MODULE_NAMESPACE=Modules +COMPRESSION_ENABLED=false +COMPRESSION_LEVEL_GZIP=-1 +COMPRESSION_LEVEL_BROTLI=4 + +CORS_ALLOWED_ORIGINS=* +CORS_ALLOWED_HEADERS="Content-Type, Authorization" +CORS_ALLOWED_METHODS="GET, POST, PUT, PATCH, DELETE, OPTIONS" + diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..cf400b1 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,19 @@ +in(__DIR__ . '/src') + ->in(__DIR__ . '/modules') + ->in(__DIR__ . '/tests') + ->in(__DIR__ . '/config'); + +$config = new PhpCsFixer\Config(); +return $config->setRules([ + '@PSR12' => true, + 'strict_param' => true, + 'array_syntax' => ['syntax' => 'short'], + 'no_unused_imports' => true, + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'single_quote' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], +]) + ->setFinder($finder); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cc2a5b8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contributing to Phred + +Thank you for your interest in Phred! We welcome contributions of all kinds. + +## RFC Process + +For major changes, please open an issue first to discuss what you would like to change. + +1. Create a "Request for Comments" (RFC) issue. +2. Wait for feedback from the core maintainers. +3. Once approved, submit a Pull Request. + +## Pull Request Process + +1. Fork the repository and create your branch from `main`. +2. If you've added code that should be tested, add tests. +3. Ensure the test suite passes (`composer test`). +4. Ensure static analysis passes (`composer analyze`). +5. Ensure code style matches PSR-12 (`composer fix`). +6. Update the documentation if necessary. + +## Code of Conduct + +Please be respectful and professional in all interactions. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f783b8d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM php:8.2-fpm + +RUN apt-get update && apt-get install -y \ + libpq-dev \ + libzip-dev \ + zip \ + unzip \ + git \ + && docker-php-ext-install pdo_mysql pdo_pgsql zip + +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +WORKDIR /var/www + +COPY . . + +RUN composer install --no-dev --optimize-autoloader + +RUN chown -R www-data:www-data storage bootstrap/cache + +EXPOSE 9000 +CMD ["php-fpm"] diff --git a/MILESTONES.md b/MILESTONES.md index a68572b..78be08d 100644 --- a/MILESTONES.md +++ b/MILESTONES.md @@ -6,19 +6,19 @@ Phred supports REST and JSON:API via env setting; batteries-included defaults, s [↑ Back to Top](#table-of-contents) ## Table of Contents -- [~~M0 — Project bootstrap (repo readiness)~~](#m0-project-bootstrap-repo-readiness) -- [~~M1 — Core HTTP kernel and routing~~](#m1-core-http-kernel-and-routing) -- [~~M2 — Configuration and environment~~](#m2-configuration-and-environment) -- [~~M3 — API formats and content negotiation~~](#m3-api-formats-and-content-negotiation) -- [~~M4 — Error handling and problem details~~](#m4-error-handling-and-problem-details) -- [~~M5 — Dependency Injection and Service Providers~~](#m5-dependency-injection-and-service-providers) -- [~~M6 — MVC: Controllers, Views, Templates~~](#m6-mvc-controllers-views-templates) -- [~~M7 — Modules (Django‑style app structure)~~](#m7-modules-django-style-app-structure) -- [~~M8 — Database access, migrations, and seeds~~](#m8-database-access-migrations-and-seeds) -- [~~M9 — CLI (phred) and scaffolding~~](#m9-cli-phred-and-scaffolding) -- [~~M10 — Security middleware and auth primitives~~](#m10-security-middleware-and-auth-primitives) -- [~~M11 — Logging, HTTP client, and filesystem~~](#m11-logging-http-client-and-filesystem) -- [~~M12 — Serialization/validation utilities and pagination~~](#m12-serialization-validation-utilities-and-pagination) +- [M0 — Project bootstrap (repo readiness)](#m0-project-bootstrap-repo-readiness) +- [M1 — Core HTTP kernel and routing](#m1-core-http-kernel-and-routing) +- [M2 — Configuration and environment](#m2-configuration-and-environment) +- [M3 — API formats and content negotiation](#m3-api-formats-and-content-negotiation) +- [M4 — Error handling and problem details](#m4-error-handling-and-problem-details) +- [M5 — Dependency Injection and Service Providers](#m5-dependency-injection-and-service-providers) +- [M6 — MVC: Controllers, Views, Templates](#m6-mvc-controllers-views-templates) +- [M7 — Modules (Django‑style app structure)](#m7-modules-django-style-app-structure) +- [M8 — Database access, migrations, and seeds](#m8-database-access-migrations-and-seeds) +- [M9 — CLI (phred) and scaffolding](#m9-cli-phred-and-scaffolding) +- [M10 — Security middleware and auth primitives](#m10-security-middleware-and-auth-primitives) +- [M11 — Logging, HTTP client, and filesystem](#m11-logging-http-client-and-filesystem) +- [M12 — Serialization/validation utilities and pagination](#m12-serialization-validation-utilities-and-pagination) - [M13 — OpenAPI and documentation](#m13-openapi-and-documentation) - [M14 — Testing, quality, and DX](#m14-testing-quality-and-dx) - [M15 — Caching and performance (optional default)](#m15-caching-and-performance-optional-default) @@ -28,241 +28,238 @@ Phred supports REST and JSON:API via env setting; batteries-included defaults, s - [M19 — Documentation site](#m19-documentation-site) - [M20 — Dynamic Command Help](#m20-dynamic-command-help) - [M21 — Governance and roadmap tracking](#m21-governance-and-roadmap-tracking) -- [Notes on sequencing and parallelization](#notes-on-sequencing-and-parallelization) -## ~~M0 — Project bootstrap (repo readiness)~~ -* ~~Tasks:~~ - * ~~Finalize `composer.json` (namespaces, scripts, suggests) and `LICENSE`.~~ - * ~~Add `.editorconfig`, `.gitattributes`, `.gitignore`, example `.env.example`.~~ - * ~~Set up CI (lint, static analysis, unit tests) and basic build badge.~~ -* ~~Acceptance:~~ - * ~~Fresh clone installs (without running suggested packages) and passes linters/analysis/tests.~~ +## M0 — Project bootstrap (repo readiness) +* Tasks: + - [x] Finalize `composer.json` (namespaces, scripts, suggests) and `LICENSE`. + - [x] Add `.editorconfig`, `.gitattributes`, `.gitignore`, example `.env.example`. + - [x] Set up CI (lint, static analysis, unit tests) and basic build badge. +* Acceptance: + - [x] Fresh clone installs (without running suggested packages) and passes linters/analysis/tests. [↑ Back to Top](#table-of-contents) -## ~~M1 — Core HTTP kernel and routing~~ -* ~~Tasks:~~ - * ~~Implement the HTTP kernel: `PSR-15` pipeline via `Relay`.~~ - * ~~Wire `nyholm/psr7(-server)` factories and server request creation.~~ - * ~~Integrate `nikic/fast-route` with a RouteCollector and dispatcher.~~ - * ~~Define route → controller resolution (invokable controllers).~~ - * ~~Add minimal app bootstrap (front controller) and DI container wiring (`PHP-DI`).~~ - * ~~Addendum: Route groups (prefix only) via `Router::group()`~~ -* ~~Acceptance:~~ - * ~~Sample route returning a JSON 200 via controller.~~ - * ~~Controllers are invokable (`__invoke(Request)`), one route per controller.~~ - * ~~Route groups (prefix only) work and are tested.~~ +## M1 — Core HTTP kernel and routing +* Tasks: + - [x] Implement the HTTP kernel: `PSR-15` pipeline via `Relay`. + - [x] Wire `nyholm/psr7(-server)` factories and server request creation. + - [x] Integrate `nikic/fast-route` with a RouteCollector and dispatcher. + - [x] Define route → controller resolution (invokable controllers). + - [x] Add minimal app bootstrap (front controller) and DI container wiring (`PHP-DI`). + - [x] Addendum: Route groups (prefix only) via `Router::group()` +* Acceptance: + - [x] Sample route returning a JSON 200 via controller. + - [x] Controllers are invokable (`__invoke(Request)`), one route per controller. + - [x] Route groups (prefix only) work and are tested. [↑ Back to Top](#table-of-contents) -## ~~M2 — Configuration and environment~~ -* ~~Tasks:~~ - * ~~Load `.env` via `vlucas/phpdotenv` and expose `Phred\Support\Config`.~~ - * ~~Define configuration precedence and document keys (e.g., `API_FORMAT`, `APP_ENV`, `APP_DEBUG`).~~ -* ~~Acceptance:~~ - * ~~App reads config from `.env`; unit test demonstrates override behavior.~~ +## M2 — Configuration and environment +* Tasks: + - [x] Load `.env` via `vlucas/phpdotenv` and expose `Phred\Support\Config`. + - [x] Define configuration precedence and document keys (e.g., `API_FORMAT`, `APP_ENV`, `APP_DEBUG`). +* Acceptance: + - [x] App reads config from `.env`; unit test demonstrates override behavior. [↑ Back to Top](#table-of-contents) -## ~~M3 — API formats and content negotiation~~ -* ~~Tasks:~~ - * ~~Finalize `ContentNegotiationMiddleware` using `.env` and `Accept` header.~~ - * ~~Bind `ApiResponseFactoryInterface` to `RestResponseFactory` or `JsonApiResponseFactory` based on format.~~ - * ~~Provide developer‑facing helpers for common responses (`ok`, `created`, `error`).~~ -* ~~Acceptance:~~ - * ~~Demo endpoints respond correctly as REST or JSON:API depending on `API_FORMAT` and `Accept`.~~ +## M3 — API formats and content negotiation +* Tasks: + - [x] Finalize `ContentNegotiationMiddleware` using `.env` and `Accept` header. + - [x] Bind `ApiResponseFactoryInterface` to `RestResponseFactory` or `JsonApiResponseFactory` based on format. + - [x] Provide developer‑facing helpers for common responses (`ok`, `created`, `error`). +* Acceptance: + - [x] Demo endpoints respond correctly as REST or JSON:API depending on `API_FORMAT` and `Accept`. [↑ Back to Top](#table-of-contents) -## ~~M4 — Error handling and problem details~~ -* ~~Tasks:~~ - * ~~Finalize `ProblemDetailsMiddleware` with RFC7807 (REST) and JSON:API error documents.~~ - * ~~Integrate `filp/whoops` for dev mode (`APP_DEBUG=true`).~~ - * ~~Map common exceptions to HTTP status codes; include correlation/request IDs in responses/logs.~~ -* ~~Acceptance:~~ - * ~~Throwing an exception yields a standards‑compliant error response; debug mode shows Whoops page.~~ +## M4 — Error handling and problem details +* Tasks: + - [x] Finalize `ProblemDetailsMiddleware` with RFC7807 (REST) and JSON:API error documents. + - [x] Integrate `filp/whoops` for dev mode (`APP_DEBUG=true`). + - [x] Map common exceptions to HTTP status codes; include correlation/request IDs in responses/logs. +* Acceptance: + - [x] Throwing an exception yields a standards‑compliant error response; debug mode shows Whoops page. [↑ Back to Top](#table-of-contents) -## ~~M5 — Dependency Injection and Service Providers~~ -* ~~Tasks:~~ - * ~~Define Service Provider interface and lifecycle (register, boot).~~ - * ~~Module discovery loads providers in order (core → app → module).~~ - * ~~Add examples for registering controllers, services, config, and routes via providers.~~ - * ~~Define contracts: `Template\Contracts\RendererInterface`, `Orm\Contracts\*`, `Flags\Contracts\FeatureFlagClientInterface`, `Testing\Contracts\TestRunnerInterface`.~~ - * ~~Define config/env keys for driver selection (e.g., `TEMPLATE_DRIVER`, `ORM_DRIVER`, `FLAGS_DRIVER`, `TEST_RUNNER`).~~ - * ~~Provide “default adapter” Service Providers for the shipped packages and document swap procedure.~~ -* ~~Acceptance:~~ - * ~~Providers can contribute bindings and routes; order is deterministic and tested.~~ - * ~~Drivers can be switched via `.env`/config without changing controllers/services; example provider route covered by tests.~~ +## M5 — Dependency Injection and Service Providers +* Tasks: + - [x] Define Service Provider interface and lifecycle (register, boot). + - [x] Module discovery loads providers in order (core → app → module). + - [x] Add examples for registering controllers, services, config, and routes via providers. + - [x] Define contracts: `Template\Contracts\RendererInterface`, `Orm\Contracts\*`, `Flags\Contracts\FeatureFlagClientInterface`, `Testing\Contracts\TestRunnerInterface`. + - [x] Define config/env keys for driver selection (e.g., `TEMPLATE_DRIVER`, `ORM_DRIVER`, `FLAGS_DRIVER`, `TEST_RUNNER`). + - [x] Provide “default adapter” Service Providers for the shipped packages and document swap procedure. +* Acceptance: + - [x] Providers can contribute bindings and routes; order is deterministic and tested. + - [x] Drivers can be switched via `.env`/config without changing controllers/services; example provider route covered by tests. [↑ Back to Top](#table-of-contents) -## ~~M6 — MVC: Controllers, Views, Templates~~ -* ~~Tasks:~~ - * ~~Controller base class and conventions (request/response helpers).~~ - * ~~View layer (data preparation) with `getphred/eyrie` template engine integration.~~ - * ~~Template rendering helper: `$this->render(