承接 componenta/skeleton 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

componenta/skeleton

Composer 安装命令:

composer create-project componenta/skeleton

包简介

Componenta Framework application skeleton

README 文档

README

Componenta Skeleton is the starter distribution of Componenta Framework for PHP 8.4+ applications. It provides a ready project with entry points, configuration, container wiring, error handling, class discovery, and HTTP, API, CLI, and WebSocket presets.

The skeleton shows how the framework assembles an application from Componenta packages: Composer discovers package providers, configuration merges them with project files, the container builds services, and Runner starts the selected execution scope: HTTP, CLI, or WebSocket.

Installation

composer create-project componenta/skeleton my-app

During composer create-project, Installer::install() runs. In interactive mode it asks questions; in non-interactive mode it uses defaults. After the preset is selected, it adds the matching Composer requirements, writes entry points, and removes installer-only files.

Details: componenta/composer-plugin describes Composer provider discovery, and componenta/app describes the application runtime.

Presets

Preset Created files and behavior
Web HTTP application with public/index.php, routing, templates, config/routes.php, config/pipeline.php, and composer serve.
API HTTP application with routing and a JSON welcome response, without template files.
CLI Console application without HTTP, WebSocket, routing, or public entry point.
WebSocket WebSocket application with bin/websocket.php and WebSocket configuration, without HTTP public entry point.

In interactive mode, HTTP presets ask for a PSR-7 implementation: Nyholm, Diactoros, Guzzle, or Slim. The Web preset also asks for a template renderer. The test runner is selected during installation: Pest by default, or PHPUnit.

In non-interactive mode, the installer uses the Web preset with Nyholm PSR-7, templates, Pest, CQRS, and policies. Authentication, Cycle ORM, and the WebSocket add-on are disabled by default.

Details: componenta/http-psr describes HTTP factories, componenta/http-psr-nyholm documents one PSR-7 integration, and componenta/templater-app describes template integration.

Installer Options

After the preset is selected, the installer configures project requirements and files:

  • HTTP presets create public/index.php, config/routes.php, config/pipeline.php, src/Welcome.php, and the safe error template templates/error/500.phtml;
  • the Web preset creates templates/welcome.phtml and installs componenta/templater-app when templates are selected;
  • the CLI preset does not create HTTP or WebSocket infrastructure;
  • the WebSocket preset creates bin/websocket.php, config/websocket.php, and the starter application src/WebSocket/WelcomeApplication.php;
  • CQRS, policies, authentication, Cycle ORM, and the WebSocket add-on can be enabled or disabled interactively;
  • when authentication is enabled, CQRS and policies are forced on;
  • on Windows, console.cmd is created as a wrapper for bin/console.php.

Details: componenta/cqrs-app describes command and query integration, componenta/policy-app describes policy integration, componenta/auth describes authentication, and componenta/cycle-app describes Cycle ORM.

Application Lifecycle

  1. The outer entry point (public/index.php, bin/console.php, or bin/websocket.php) loads Composer autoload, creates a PathResolver, and invokes the root index.php.
  2. The root index.php receives a Scope: Scope::HTTP, Scope::CLI, or Scope::WEBSOCKET.
  3. config/container.php calls ConfigFactory::create() and ContainerFactory::create().
  4. config/config.php returns a ConfigDefinition: package config providers and discovery directories.
  5. The container is built from configuration, discovered classes, and services contributed by packages.
  6. Runner::run() selects the adapter for the current scope and starts the application.
  7. Bootloaders prepare the selected scope by registering commands, routes, handlers, listeners, templates, or WebSocket applications.

The order is the same for every preset. Only the execution scope and installed integration packages change.

Details: componenta/app describes Scope, Runner, adapters, and bootloaders; componenta/app-http describes HTTP scope; componenta/app-console describes CLI scope; componenta/websocket-app describes WebSocket scope.

Configuration

The main configuration file is config/config.php. It loads three sources:

return new ConfigDefinition(
    providers: [
        new ComposerPackageConfigProvider($paths->resolve('config/componenta-providers.php')),
        new AttributeConfigProvider(),
        new FileProvider($paths->resolve('config/autoload/{{,*.}global,{,*.}local}.{php,yaml,json}')),
    ],
    discovery: new DiscoveryDefinition(
        directories: ['src', 'bin/command'],
        exclude: ['Cycle/Migrations'],
    ),
);

ComposerPackageConfigProvider loads providers generated from installed packages. AttributeConfigProvider loads configuration from attributes discovered in project code. FileProvider loads project files from config/autoload.

*.global.* files are shared project configuration. *.local.* files are local environment configuration and are normally not committed. The installer creates config/autoload/app.local.php from config/autoload/app.local.php.dist.

Details: componenta/config describes config providers and file loading, and componenta/app describes ConfigFactory, environment handling, and cache layout.

Config Providers And #[AsConfig]

A config provider is a callable class that returns a configuration array. Packages use providers to register factories, aliases, autowiring, bootloaders, middleware, compiler contributors, and their own config keys.

Package providers are wired by componenta/composer-plugin: each package declares provider classes in extra.componenta.config-providers, the plugin collects them into config/componenta-providers.php, and ComposerPackageConfigProvider loads that file.

Project config can live in src/ and be marked with #[AsConfig]. The skeleton ships App\ConfigProvider this way:

namespace App;

use Componenta\App\Config\AsConfig;

#[AsConfig]
final class ConfigProvider extends \Componenta\Config\ConfigProvider
{
    protected function getConfig(): array
    {
        return [
            'app' => [
                'name' => 'My Application',
            ],
        ];
    }
}

AttributeConfigProvider finds classes marked with #[AsConfig] in discovery directories, creates the class without constructor arguments, invokes it, and merges the returned array into application config. The provider must return an array or iterable. Use config/autoload/*.local.php for machine-local settings; use #[AsConfig] for package or application-module configuration.

#[AsConfig] can target classes, functions, or methods, but the current AttributeConfigProvider scans discovered classes and invokes providers placed on classes. In the skeleton, the supported primary pattern is a class with __invoke() under src/.

Details: componenta/config describes the base ConfigProvider, and componenta/app describes AttributeConfigProvider and discovery.

Package Discovery

Componenta packages declare config providers in composer.json under extra.componenta.config-providers. componenta/composer-plugin reads this metadata after composer install, composer update, and composer dump-autoload, then writes config/componenta-providers.php.

The generated file returns an array of provider classes and must not be edited manually. The installer does not write it directly: the file appears or changes when the Composer plugin runs. Before the plugin runs for the first time, the file may be missing; ComposerPackageConfigProvider then returns an empty configuration. When a package is removed from Composer, its provider disappears from the generated file on the next Composer event.

Details: componenta/composer-plugin describes metadata format, Composer events, and atomic provider file writes.

Class Discovery

DiscoveryDefinition tells the framework which directories to scan in development mode. The skeleton scans src and bin/command. Discovered classes are passed to packages that understand attributes:

  • componenta/app-console discovers console commands marked with #[AsCommand];
  • componenta/router-app discovers HTTP routes;
  • componenta/cqrs-app discovers command and query handlers;
  • componenta/policy-app prepares policy maps;
  • componenta/interceptor-app prepares interceptor maps.

In development mode, discovery results and derived maps are cached in var/cache/dev. In production-like modes, the application should read prepared files from var/cache/build.

Details: componenta/class-finder describes class discovery; componenta/router-app, componenta/cqrs-app, componenta/policy-app, and componenta/interceptor-app describe their discovery maps.

Development And Build Modes

By default .env.dist contains:

APP_ENV=development
APP_DEBUG=true

With APP_ENV=development, the application assembles configuration from providers, files, and attributes, scans configured directories, and uses development caches to make repeated starts faster.

When APP_ENV is not development, ConfigFactory assumes the application runs from build cache and reads var/cache/build/config.cache.php. In that mode, the project config definition is not rebuilt on every request. The build cache must exist before the application is started in that environment; otherwise startup fails with a configuration error.

With APP_ENV=production, the container also tries to use prepared var/cache/build/container.cache.php and var/cache/build/container.factory.php files when they exist.

APP_DEBUG controls whether detailed error information is shown to users. HTTP applications use templates/error/500.phtml as the safe error page; detailed error output is enabled only when APP_DEBUG=true.

Details: componenta/app describes ConfigFactory, CacheLayout, and compile support; componenta/error-handler-app describes HTTP error handling and safe rendering.

Container

config/container.php is the application composition point. It loads configuration and returns a PSR-11 container:

$result = ConfigFactory::create(
    paths: $paths,
    definition: static fn () => require $paths->resolve('config/config.php'),
);

return ContainerFactory::create($paths, $result->config, $result->discovered);

ContainerFactory adds PathResolverInterface, discovered classes, and services declared by providers. The project can extend the container through config/autoload/*.php files or through App\ConfigProvider marked with #[AsConfig].

Details: componenta/di describes the DI container, factories, attributes, and property resolvers; componenta/config describes config array shape.

Application Config

The final application configuration is represented by Componenta\Config\Config. ConfigFactory::create() creates it, and ContainerFactory stores the same object in the container under Config::class and the 'config' alias.

use Componenta\Config\Config;
use Componenta\Config\ConfigPath;

/** @var \Psr\Container\ContainerInterface $container */
$config = $container->get(Config::class);

$name = $config->string(new ConfigPath('app.name'), 'Componenta App');
$debug = $config->bool(new ConfigPath('app.debug'), false);

Services can receive the full config through constructor injection:

namespace App\Service;

use Componenta\Config\Config;
use Componenta\Config\ConfigPath;

final readonly class FeatureFlags
{
    public function __construct(
        private Config $config,
    ) {}

    public function enabled(string $name): bool
    {
        return $this->config->bool(new ConfigPath("features.$name"), false);
    }
}

If a service needs one value, use the DI #[Config] attribute. A string key is read literally, while ConfigPath enables dot-notation traversal of nested arrays:

namespace App\Service;

use Componenta\Config\ConfigPath;
use Componenta\DI\Attribute\Config;

final readonly class MailerOptions
{
    public function __construct(
        #[Config(new ConfigPath('mail.from'))]
        public string $from,

        #[Config(new ConfigPath('mail.retries'), default: 3)]
        public int $retries,
    ) {}
}

Main Config methods:

Method Purpose
`get(string ConfigPath $key, mixed $default = DefaultValue::None)`
`has(string ConfigPath $key)`
string(), int(), float(), bool(), array() Return a value with type conversion.
`only(string ConfigPath
`except(string ConfigPath
toArray() Returns the full config array.

get('database.host') looks for the literal $config['database.host'] key. Nested access requires new ConfigPath('database.host'), which reads $config['database']['host'].

Config also exposes the environment property. Use it to read variables loaded from .env or the process environment:

$env = $config->environment;

$isProduction = $env?->match('APP_ENV', 'production') ?? false;
$timezone = $env?->string('APP_TIMEZONE', 'UTC') ?? 'UTC';

Config files and providers usually return arrays instead of reading Config. Reading the assembled Config belongs in services, factories, and bootloaders after all providers have been merged.

Details: componenta/config describes Config, ConfigPath, Environment, file loading, and merge rules; componenta/di describes the #[Config] attribute.

Application Bootloaders

An application bootloader runs startup work before the current scope starts: HTTP, CLI, or WebSocket. Use bootloaders for work that needs the built container and the prepared application target: wiring the HTTP pipeline, registering console commands, restoring discovery maps, assigning a WebSocket application, or running application warmup.

Runner creates BootContext, BootloaderProvider reads class names from ConfigKey::BOOTLOADERS, filters them by Scope, resolves matching bootloaders from the container, and calls boot():

use Componenta\App\ConfigKey;

return [
    ConfigKey::BOOTLOADERS => [
        App\Boot\WarmupBootloader::class,
    ],
];

In the skeleton, this registration lives in src/ConfigProvider.php, which is marked with #[AsConfig]. To add a custom bootloader, add it to getConfig() and register the class as an autowired service:

namespace App;

use App\Boot\WarmupBootloader;
use App\Service\WarmupService;
use Componenta\App\Config\AsConfig;
use Componenta\App\ConfigKey;

#[AsConfig]
final class ConfigProvider extends \Componenta\Config\ConfigProvider
{
    protected function getConfig(): array
    {
        return [
            ConfigKey::BOOTLOADERS => [
                WarmupBootloader::class,
            ],
        ];
    }

    protected function getAutowires(): array
    {
        return [
            WarmupBootloader::class,
            WarmupService::class,
        ];
    }
}

Application bootloaders can extend the base Bootloader class. In that mode __invoke() is called through DI, so method parameters can be resolved from the container:

namespace App\Boot;

use App\Service\WarmupService;
use Componenta\App\Boot\BootContext;
use Componenta\App\Boot\Bootloader;
use Componenta\App\Scope;
use Componenta\Config\ConfigPath;

final class WarmupBootloader extends Bootloader
{
    public static function scopes(): array
    {
        return [Scope::HTTP];
    }

    public function supports(BootContext $context): bool
    {
        return $context->config->bool(new ConfigPath('warmup.enabled'), false);
    }

    public function __invoke(WarmupService $warmup): void
    {
        $warmup->run();
    }
}

If you need full control, implement BootloaderInterface directly. Inside boot(), BootContext::$container, BootContext::$config, BootContext::$scope, and BootContext::target() are available:

namespace App\Boot;

use Componenta\App\Boot\BootContext;
use Componenta\App\Boot\BootloaderInterface;
use Componenta\App\Boot\Target\HttpBootTargetInterface;
use Componenta\App\Scope;

final class ExtraHttpPipelineBootloader implements BootloaderInterface
{
    public static function scopes(): array
    {
        return [Scope::HTTP];
    }

    public function boot(BootContext $context): void
    {
        $http = $context->target(HttpBootTargetInterface::class);
        $http->pipe(\App\Http\Middleware\RequestIdMiddleware::class);
    }

    public function supports(BootContext $context): bool
    {
        return true;
    }
}

For ordinary global HTTP middleware, prefer config/pipeline.php. A custom HTTP bootloader is useful when registration depends on the container, configuration, or an integration package. For CLI use ConsoleBootTargetInterface; for WebSocket use WebSocketBootTargetInterface.

Details: componenta/app describes BootContext, BootloaderInterface, BootloaderProvider, and boot targets; componenta/app-http, componenta/app-console, and componenta/websocket-app show scope-specific bootloaders.

HTTP And Routing

HTTP presets create public/index.php, config/routes.php, and config/pipeline.php. The public entry point starts Scope::HTTP. The config/pipeline.php file defines the global HTTP pipeline:

$app->pipe(Componenta\Error\Http\Middleware\ErrorHandlerMiddleware::class);
$app->pipe(Componenta\Http\Middleware\BodyParsingMiddleware::class);

componenta/router-app uses config/routes.php as the default manual route file. The file receives $routes as a Componenta\Http\Router\Routes instance. Use it for routes that are easier to define programmatically: route groups, shared prefixes, shared middleware, shared tokens and defaults, manual RouteRecord instances, and nested groups.

use App\Http\AdminDashboard;
use App\Http\AdminUsers;
use App\Http\Middleware\RequireAdminMiddleware;
use App\Http\Middleware\RequireAuthenticationMiddleware;

/**
 * @var \Componenta\Http\Router\Routes $routes
 */

$admin = $routes->group(
    name: 'admin',
    prefix: '/admin',
    middleware: [
        RequireAuthenticationMiddleware::class,
        RequireAdminMiddleware::class,
    ],
    tokens: ['id' => '\d+'],
);

$admin->get('dashboard', '/', AdminDashboard::class);
$admin->get('users.show', '/users/{id}', AdminUsers::class);

The group prefixes route names and paths. In the example, final names are admin.dashboard and admin.users.show, and paths are /admin and /admin/users/{id}. Group settings are inherited by nested groups and routes.

Routes can be added declaratively with #[Route] or manually in config/routes.php. The starter route / lives in src/Welcome.php: when templates are selected it renders templates/welcome.phtml, otherwise it returns JSON:

{"status":"ok","message":"Componenta Framework skeleton is running."}

Details: componenta/router describes the router; componenta/router-app describes route attribute discovery; componenta/app-http describes the HTTP adapter; componenta/http describes base HTTP contracts and exceptions.

HTTP Middleware

Global middleware is registered in config/pipeline.php. HttpBootloader includes this file, and $app implements HttpBootTargetInterface. Each $app->pipe(...) call appends middleware to the application-wide HTTP pipeline:

use App\Http\Middleware\RequestIdMiddleware;
use Componenta\Error\Http\Middleware\ErrorHandlerMiddleware;
use Componenta\Http\Middleware\BodyParsingMiddleware;

/**
 * @var \Componenta\App\Boot\Target\HttpBootTargetInterface $app
 */

$app->pipe(ErrorHandlerMiddleware::class);
$app->pipe(RequestIdMiddleware::class);
$app->pipe(BodyParsingMiddleware::class);

Order matters: middleware runs in the order it is added. Error handling is usually first so it can catch exceptions from later layers. BodyParsingMiddleware must run before handlers that use #[MapRequestPayload], because it fills the parsed body on the PSR-7 request.

The base HTTP preset installs ErrorHandlerMiddleware and BodyParsingMiddleware. Additional framework packages provide ready middleware: CorsMiddleware, CsrfMiddleware, ThrottleMiddleware, and TrustedProxyMiddleware. They can be placed in the global pipeline or on specific route groups and routes when the package is installed and its provider is loaded by the Composer plugin. App\Http\Middleware\... classes in the examples below are application PSR-15 middleware classes that you create in the project.

Group middleware is registered on a route group in config/routes.php. It applies to every route in the group and is inherited by nested groups:

use App\Http\AdminDashboard;
use App\Http\AdminUsers;
use App\Http\Middleware\RequireAdminMiddleware;
use App\Http\Middleware\RequireAuthenticationMiddleware;

/**
 * @var \Componenta\Http\Router\Routes $routes
 */

$admin = $routes->group(
    name: 'admin',
    prefix: '/admin',
    middleware: [
        RequireAuthenticationMiddleware::class,
        RequireAdminMiddleware::class,
    ],
);

$admin->get('dashboard', '/', AdminDashboard::class);
$admin->get('users.show', '/users/{id}', AdminUsers::class);

Route-specific middleware is registered with the middlewares argument on #[Route] or manually with RouteRecord. Route middleware is appended after group middleware:

namespace App\Http;

use App\Http\Middleware\AuditPostAccessMiddleware;
use Componenta\Http\Router\Attribute\Route;

final class PostController
{
    #[Route(
        name: 'posts.show',
        path: '/posts/{id}',
        methods: 'GET',
        middlewares: [AuditPostAccessMiddleware::class],
        tokens: ['id' => '\d+'],
        group: 'api',
    )]
    public function show(): array
    {
        return ['status' => 'ok'];
    }
}
use App\Http\PostController;
use App\Http\Middleware\AuditPostAccessMiddleware;
use Componenta\Http\Router\RouteRecord;

/**
 * @var \Componenta\Http\Router\Routes $routes
 */

$routes->addRoute(RouteRecord::get(
    name: 'posts.show',
    path: '/posts/{id}',
    handler: [PostController::class, 'show'],
    middlewares: [AuditPostAccessMiddleware::class],
    tokens: ['id' => '\d+'],
));

Middleware definitions are resolved by componenta/middleware-factory. The common definition is a container class name. Ready MiddlewareInterface objects, RequestHandlerInterface objects, MiddlewareGroup, and callable middleware are also supported when resolvers can handle them. Plain strings such as 'auth' are not a built-in named middleware registry. If the application wants aliases like that, add a custom resolver or use class names directly.

Details: componenta/app-http describes config/pipeline.php, componenta/middleware-factory describes resolving definitions to PSR-15 middleware, componenta/router describes group and route middleware ordering. Middleware package READMEs document concrete implementations: componenta/http-body-parsing-middleware, componenta/http-cors-middleware, componenta/http-csrf-middleware, componenta/http-throttle-middleware, and componenta/http-trusted-proxy-middleware.

The #[Route] Attribute

#[Route] can be placed on an invokable class or controller method. It defines the route name, path, HTTP methods, middleware, parameter constraints, defaults, group name, and priority.

namespace App\Http;

use Componenta\DI\Attribute\RequestAttribute;
use Componenta\Http\Router\Attribute\Route;

final class PostController
{
    #[Route(
        name: 'posts.show',
        path: '/posts/{id:\\d+}',
        methods: 'GET',
        group: 'api',
        priority: 20,
    )]
    public function show(#[RequestAttribute] int $id): array
    {
        return ['id' => $id];
    }
}

methods accepts a string ('GET'), a pipe-separated string ('GET|POST'), or an array (['GET', 'POST']). middlewares accepts a string or array. tokens defines regex constraints for path parameters, and defaults defines default values.

Parameter constraints can also be written inline in the path. For example, /posts/{id:\d+} and /archive/[?year:\d+=2026] define the route token directly in the template. Explicit tokens override inline constraints when both are present.

priority controls attribute-route registration order: higher values are registered first and therefore matched first when route patterns overlap. This matters for conflicts such as /{slug} and /archive.

When #[Route] references a group, that group must be explicitly registered in config/routes.php before attribute routes are finalized:

/**
 * @var \Componenta\Http\Router\Routes $routes
 */

use App\Http\Middleware\RequireAuthenticationMiddleware;

$routes->group('api', '/api');
$routes->group('admin', '/admin', middleware: [RequireAuthenticationMiddleware::class]);

If the group is not registered, the route does not receive the group's prefix, middleware, tokens, or defaults: it is added as a normal route with the group name preserved in the record. Groups referenced by route attributes should therefore be declared explicitly in config/routes.php.

Details: componenta/router describes RouteRecord, Routes, and RouteGroup, and componenta/router-app describes AttributeRouteLocator.

HTTP Request Mapping

#[Route] only matches the URL to a handler. Path parameters such as {id} are stored as PSR-7 request attributes, but they are not automatically injected into method arguments by name. Handler parameters must use request-mapping attributes from componenta/di.

For single values, use single-value attributes:

namespace App\Http;

use Componenta\DI\Attribute\PayloadParam;
use Componenta\DI\Attribute\QueryParam;
use Componenta\DI\Attribute\RequestAttribute;
use Componenta\Http\Router\Attribute\Route;

final class PostController
{
    #[Route('posts.show', '/posts/{id}', 'GET', tokens: ['id' => '\d+'])]
    public function show(
        #[RequestAttribute] int $id,
        #[QueryParam(default: false, cast: 'bool')] bool $preview,
    ): array {
        return ['id' => $id, 'preview' => $preview];
    }

    #[Route('posts.rename', '/posts/{id}/rename', 'POST', tokens: ['id' => '\d+'])]
    public function rename(
        #[RequestAttribute] int $id,
        #[PayloadParam] string $title,
    ): array {
        return ['id' => $id, 'title' => $title];
    }
}

Main single-value attributes:

Attribute Source
#[RequestAttribute] PSR-7 request attributes. Route parameters are stored here.
#[QueryParam] Query string, for example ?page=2.
#[PayloadParam] Parsed request body. JSON and non-native form parsing require BodyParsingMiddleware; the HTTP preset includes it.
#[Header] HTTP header.
#[Cookie] Cookie.
#[UploadedFile] Uploaded file from $request->getUploadedFiles().

When no name is passed, RequestAttribute, QueryParam, and PayloadParam use the method parameter name. Therefore #[PayloadParam] string $title reads the title field, and #[RequestAttribute] int $id reads the id request attribute. Pass an explicit name only when the HTTP field differs from the argument name: #[PayloadParam('post_title')] string $title. Query string and payload values often need cast, because raw request data arrives as strings. Route parameters are already converted by the router to int or float when the matched value looks numeric.

For DTOs or arrays, use Map* attributes. They extract a data array, apply map, cast, defaults, sortMap, and exclude, validate the DTO when a validator is available, and build the object through the container:

namespace App\Http;

use Componenta\DI\Attribute\MapQueryString;
use Componenta\DI\Attribute\MapRequestPayload;
use Componenta\Http\Router\Attribute\Route;

final readonly class PostListQuery
{
    public function __construct(
        public int $page = 1,
        public ?string $tag = null,
    ) {}
}

final class MapPostListQuery extends MapQueryString
{
    protected array $cast = ['page' => 'int'];
    protected array $defaults = ['page' => 1];
}

final readonly class CreatePostCommand
{
    public function __construct(
        public string $title,
        public string $body,
    ) {}
}

final class PostController
{
    #[Route('posts.index', '/posts', 'GET')]
    public function index(#[MapPostListQuery] PostListQuery $query): array
    {
        return ['page' => $query->page, 'tag' => $query->tag];
    }

    #[Route('posts.create', '/posts', 'POST')]
    public function create(#[MapRequestPayload] CreatePostCommand $command): array
    {
        return ['title' => $command->title];
    }
}

Details: componenta/di describes request mapping, Map* attributes, casters, and DTO validation; componenta/router-app describes how a route handler is executed through the DI interceptor.

Interceptors

Interceptors wrap any PHP callable: a controller, handler, service method, or function. Use them for cross-cutting behavior that should not be duplicated in business code: logging, metrics, transactions, authorization, caching, parameter normalization, result serialization, or exception handling.

The base componenta/interceptor package contains the runtime layer:

Component Purpose
InterceptorInterface Contract for one interceptor. Receives CallableContextInterface and ContextHandlerInterface.
InterceptingExecutor Callable executor with an interceptor chain. Implements CallableExecutorInterface and PipelineInterface.
AttributeInterceptor Reads #[Intercept] attributes from a callable and adds declared interceptors to the chain.
ParameterResolvingInterceptor Resolves callable parameters through DI before later interceptors run.
CallbackInterceptorFactory Creates closure-based interceptors: before(), after(), catch(), finally(), around().
#[Intercept] Function or method attribute that attaches an interceptor class with constructor parameters.
ScopedInterface and Scope Restrict an interceptor to HTTP, CONSOLE, GRPC, QUEUE, or WEBSOCKET execution.

Example attribute interceptor:

use Componenta\DI\Attribute\RequestAttribute;
use Componenta\Interceptor\Attribute\Intercept;

final class UserController
{
    #[Intercept(LogCallInterceptor::class, ['channel' => 'http'])]
    #[Intercept(SerializeResultInterceptor::class)]
    public function show(#[RequestAttribute] int $id): User
    {
        // ...
    }
}

Attributes execute as layers from outside to inside: the top #[Intercept] is the outer layer, and the bottom one is closest to the callable body. An interceptor can call $handler->handle($context), modify the context or result, catch an exception, or short-circuit the chain by returning without invoking the original callable.

componenta/router-app uses interceptors for HTTP route handlers. It creates InterceptorConfigKey::HTTP_PIPELINE from ParameterResolvingInterceptor and AttributeInterceptor, so controller parameters are resolved through DI first, then #[Intercept] attributes are applied.

componenta/interceptor-app does not execute interceptors. It compiles interceptor attributes into a map for build cache so production-like runs do not need to read attributes through reflection on every request.

Details: componenta/interceptor describes interceptor execution, componenta/interceptor-app describes build-cache integration, and componenta/router-app describes HTTP handler integration with interceptors.

Console Commands

The CLI preset and other presets with componenta/app-console use bin/console.php. Commands can be registered in two ways:

  • add the command class to config/console.php;
  • mark a command with #[AsCommand] when it is inside a discovery directory.

config/console.php is still loaded on every CLI start, so the file must exist even when most commands are discovered through attributes. The skeleton keeps AboutCommand::class there explicitly.

The skeleton includes:

php bin/console.php app:about

Standard Symfony Console commands are also available, for example:

php bin/console.php list

Details: componenta/app-console describes the command registry, CLI bootloader, and command events.

Application Commands And Queries

When componenta/cqrs-app is installed, application behavior can be modeled as commands and queries. A command changes state; a query reads data. Their handlers are registered by CQRS packages and can be discovered automatically.

HTTP controllers, console commands, and other entry points should not know the execution details of a business action. They create a command or query and pass it to the matching bus.

Details: componenta/cqrs describes the command bus, query bus, operations, middleware, and async execution; componenta/cqrs-app describes CQRS discovery and compile-cache integration.

Policies

When componenta/policy-app is installed, access checks are described as policies and policy attributes on application actions. This keeps authorization outside command and query handlers.

A typical flow is: an entry point creates a command or query, CQRS middleware resolves the current actor, componenta/policy checks the policy for that action, and execution continues to the handler only when access is allowed.

Details: componenta/policy describes policies, providers, and attributes; componenta/policy-app describes compile integration; componenta/cqrs describes policy middleware placement.

Templates

When a template renderer is selected during installation, the Web preset installs componenta/templater-app. The project gets a templates/ directory, the view() helper, and templates/welcome.phtml.

HTTP error templates live in templates/error/. The safe 500 page must be available even when detailed error output is disabled.

Details: componenta/templater describes renderer contracts, and componenta/templater-app describes the view() helper and application integration.

WebSocket

The WebSocket preset creates the separate entry point bin/websocket.php and starts Scope::WEBSOCKET. HTTP infrastructure is not created for this preset. When WebSocket support is added as an extra feature to an HTTP project, the installer adds the serve:websocket Composer script.

Details: componenta/websocket-server describes the base socket server, and componenta/websocket-app describes WebSocket scope integration.

Available Commands And Composer Scripts

Scripts depend on the selected preset:

Command Available when Purpose
composer serve HTTP presets Starts PHP built-in web server on localhost:8000 with public/ as document root.
composer serve:websocket WebSocket preset or WebSocket add-on Starts bin/websocket.php.
composer test Always Runs the selected test runner: Pest or PHPUnit.
composer analyse Always Runs PHPStan over directories created by the selected preset.
php bin/console.php app:about componenta/app-console is installed Shows basic application information.
php bin/console.php list componenta/app-console is installed Lists available console commands.

The CLI preset does not create public/, config/routes.php, config/pipeline.php, or WebSocket files. HTTP presets create config/routes.php and enable routing through installed packages.

Details: componenta/app-console describes CLI support, and componenta/router-app describes HTTP route discovery.

Project Layout

Path Purpose
.env Local environment file created from .env.dist.
config/config.php Main declaration of providers and discovery.
config/container.php Application container assembly.
config/componenta-providers.php Generated provider list for installed packages. Created by componenta/composer-plugin.
config/autoload/ Project configuration from *.global.* and *.local.* files.
src/ Application code under the App\ namespace.
bin/ CLI entry point.
bin/command/ Console commands under the Console\Command\ namespace.
public/ HTTP entry point. Created only for HTTP presets.
templates/ Application and error templates. Created when templates or an HTTP safe error page are needed.
var/cache/dev/ Development caches.
var/cache/build/ Build cache for environments outside development.
var/cache/runtime/ Runtime caches.
log/ Application logs.
storage/ Application files.

Details: componenta/path-resolver describes project-root path resolution, and componenta/app describes cache layout.

Related Packages

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-14

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固