Phred/README.md
Funky Waddle fd1c9d23df refactor(core): enforce SOLID across HTTP pipeline; add small contracts and defaults; align tests
- Introduce small interfaces and default adapters (DIP):
  - Support\Contracts\ConfigInterface + Support\DefaultConfig
  - Http\Contracts\ErrorFormatNegotiatorInterface + Http\Support\DefaultErrorFormatNegotiator
  - Http\Contracts\RequestIdProviderInterface + Http\Support\DefaultRequestIdProvider
  - Http\Contracts\ExceptionToStatusMapperInterface + Http\Support\DefaultExceptionToStatusMapper
- Kernel: bind new contracts in the container; keep DelegatingApiResponseFactory wiring
- ContentNegotiationMiddleware: depend on ConfigInterface + negotiator; honor Accept for JSON:API
- ProblemDetailsMiddleware: inject negotiator + config; split into small helpers; deterministic content negotiation; stable Whoops HTML; include X-Request-Id
- DispatchMiddleware: SRP refactor into small methods; remove hidden coupling; normalize non-Response returns
- Add/adjust tests:
  - tests/ErrorHandlingTest.php for problem details, JSON:API errors, and Whoops HTML
  - tests/ContentNegotiationTest.php for format selection
  - tests/MakeCommandTest.php aligned with create:command scaffolder
- Docs/Meta: update README and MILESTONES; .gitignore to ignore .junie.json

No runtime behavior changes intended beyond clearer DI boundaries and content-negotiation determinism. All tests green.
2025-12-15 09:15:49 -06:00

7.7 KiB

Phred

A PHP MVC framework:

  • Intended for projects of all sizes, and solo or team development.
    • The single router call per controller style makes it easy for teamwork without stepping on each others toes.
  • REQUIREMENTS
    • PHP 8.1+
    • Primarily meant for Apache/Nginx webservers, will look into supporting other webservers in the future.
  • PSR-4 autoloading.
  • Installed through Composer (composer create-project getphred/phred)
  • Environment variables (.env) for configuration.
  • Supports two API formats (with content negotiation)
    • Pragmatic REST (default)
    • JSON:API
    • Choose via .env:
      • API_FORMAT=rest (plain JSON responses, RFC7807 error format)
      • API_FORMAT=jsonapi (JSON:API compliant documents and error objects)
    • Or negotiate per request using the Accept header:
      • Accept: application/vnd.api+json forces JSON:API for that request
  • TESTING environment variables (.env)
    • TEST_RUNNER=codeception
    • TEST_PATH=tests
      • TEST_PATH is relative to both project root and each module root.
  • Dependency Injection
  • Fully Pluggable, but ships with defaults:
    • Pluggability model
      • Core depends on Phred contracts (Phred\Contracts\*) and PSRs
      • Concrete implementations are provided by Service Providers.
      • Swap packages by changing .env and enabling a provider.
    • Driver keys (examples)
      • ORM_DRIVER=pairity|doctrine
      • TEMPLATE_DRIVER=eyrie|twig|plates
      • FLAGS_DRIVER=flagpole|unleash
      • TEST_RUNNER=codeception
    • Primary contracts
      • Template\RendererInterface
      • Orm\EntityManagerInterface (or repositories)
      • Flags\FeatureFlagClientInterface
      • Testing\TestRunnerInterface.
    • Default Plug-ins
      • Feature Flags through getphred/flagpole
      • ORM through getphred/pairity (handles migrations, seeds, and db access)
      • Unit Testing through codeception/codeception
        • Testing is provided as a CLI dev capability only; it is not part of the HTTP request lifecycle.
      • Template Engine through getphred/eyrie
  • Other dependencies:
    • Dependency Injection through php-di/php-di
    • Static Analysis through phpstan/phpstan
    • Code Style Enforcement through friendsofphp/php-cs-fixer
    • Logging through monolog/monolog
    • Config and environment handling through vlucas/phpdotenv
    • HTTP client through guzzlehttp/guzzle
  • CONTROLLERS
    • Invokable controllers (Actions),
    • Single router call per controller,
    • public function __invoke(Request $request) method entry point on controller class,
    • Response helpers via dependency injection:
      • Inject Phred\Http\Contracts\ApiResponseFactoryInterface to build responses consistently across formats.
      • The factory is negotiated per request (env default or Accept header) and sets appropriate Content-Type.
      • Common methods: ok(array $data), created(array $data, ?string $location), noContent(), error(int $status, string $title, ?string $detail, array $extra = []).
      • Example:
        use Phred\Http\Contracts\ApiResponseFactoryInterface as Responses;
        use Psr\Http\Message\ServerRequestInterface as Request;
        use Phred\Http\Middleware\ContentNegotiationMiddleware as Negotiation;
        
        final class ExampleController {
            public function __construct(private Responses $responses) {}
            public function __invoke(Request $request) {
                $format = $request->getAttribute(Negotiation::ATTR_API_FORMAT, 'rest');
                return $this->responses->ok(['format' => $format]);
            }
        }
        
  • VIEWS
    • Classes for data manipulation/preparation before rendering Templates,
      • $this->render(<template_name>, <data_array>); to render a template.
  • SERVICES
    • for business logic.
  • SERVICE PROVIDERS
    • for dependency injection.
  • MIGRATIONS
    • for database changes.
  • Modular separation, similar to Django apps.
    • Nested Models
    • Nested Controllers
    • Nested Views
    • Nested Services
    • Nested Migrations
    • Nested Service Providers
    • Nested Routes
    • Nested Templates
    • Nested Tests
  • CLI Helper called phred
    • php phred create:command <name> // Creates a CLI command under `console/commands
    • php phred create:module <name> // Creates a module
    • php phred create:<module>:controller // Creates a controller in the specified module
    • php phred create:<module>:model // Creates a model in the specified module
    • php phred create:<module>:migration // Creates a migration in the specified module
    • php phred create:<module>:seed // Creates a seeder in the specified module
    • php phred create:<module>:test // Creates a test in the specified module
    • php phred create:<module>:view / Creates a view in the specified module
    • php phred db:backup // Backup the database
    • php phred db:restore -f <db_backup_file> // Restore the database from the specified backup file
    • php phred migrate [-m <module>] // Migrate entire project or module
    • php phred migration:rollback [-m <module>] // Rollback entire project or module
    • php phred seed
    • php phred seed:rollback
    • php phred test[:<module>] // Test entire project or module
      • Runs tests using the configured test runner (dev only).
      • Requires require-dev dependencies.
    • php phred run [-p <port>]
      • Spawns a local PHP webserver on port 8000 (unless specified otherwise using -p)
  • CLI Helper is extendable through CLI Commands.

Command discovery

  • Core commands (bundled with Phred) are discovered from src/commands.
  • User/project commands are discovered from console/commands in your project root.

Run the CLI:

php phred list

Add your own command by creating a PHP file under console/commands, returning an instance of Phred\Console\Command (or an anonymous class extending it).

(Or by running php phred create:command <name>)

Example:

<?php
use Phred\Console\Command;
use Symfony\Component\Console\Input\InputInterface as Input;
use Symfony\Component\Console\Output\OutputInterface as Output;

return new class extends Command {
    protected string $command = 'hello:world';
    protected string $description = 'Example user command';
    public function handle(Input $in, Output $out): int { $out->writeln('Hello!'); return 0; }
};

Configuration and environment

  • Phred uses vlucas/phpdotenv to load a .env file from your project root (loaded in bootstrap/app.php).
  • Access configuration anywhere via Phred\Support\Config::get(<key>, <default>).
    • Precedence: environment variables > config files > provided default.
    • Keys may be provided in either UPPER_SNAKE (e.g., APP_ENV) or dot.notation (e.g., app.env).
    • Config files live under config/*.php and return arrays; dot keys are addressed as <file>.<path> (e.g., app.timezone).

Common keys

  • APP_ENV (default from config/app.php: local)
  • APP_DEBUG (true/false)
  • APP_TIMEZONE (default UTC)
  • API_FORMAT (rest | jsonapi; default rest)

API formats and negotiation

  • Middleware ContentNegotiationMiddleware determines the active API format per request.
  • Precedence:
    1. Accept: application/vnd.api+json → JSON:API
    2. .env/config API_FORMAT (fallback to rest)
  • The chosen format is stored on the request as phred.api_format and used by the injected ApiResponseFactoryInterface to produce the correct response shape and Content-Type.
  • Demo endpoint: GET /_phred/format responds with the active format; GET /_phred/health returns a simple JSON 200.

Examples

use Phred\Support\Config;

$env = Config::get('APP_ENV', 'local');      // reads from env, then config/app.php, else 'local'
$tz  = Config::get('app.timezone', 'UTC');   // reads nested key from config files
$fmt = strtolower(Config::get('API_FORMAT', 'rest'));