spiral/framework
Spiral Framework is a high-performance, long-running full-stack PHP framework with 60+ PSR-compatible components. Powered by RoadRunner for resident-memory apps, it supports GRPC, queues, WebSockets, background workers, and more.
Full Changelog: https://github.com/spiral/framework/compare/3.16.0...3.16.1
The most significant addition in this release is comprehensive support for PHP attributes in bootloaders, providing a modern, type-safe approach to application bootstrapping. You can now use attributes like #[SingletonMethod], #[BindMethod], #[BindScope], and priority-based lifecycle methods for cleaner and more expressive bootloader configuration.
Major performance improvements to the container's dependency resolution with iterative scope resolution, eliminating recursive overhead and reducing exception handling for faster dependency injection.
Full support for PHP 8.4, Psalm v6, Symfony Console 8, and migration to PHPUnit attributes for a modern development experience.
This major feature adds PHP attributes for configuring bootloaders, providing a more expressive and type-safe approach to application bootstrapping.
Available Attributes:
#[BindMethod] - Container bindings that create new instances#[SingletonMethod] - Container bindings that create shared instances#[InjectorMethod] - Custom injector methods#[BindAlias] - Multiple aliases for bindings#[BindScope] - Scoped bindings#[InitMethod] - Initialization phase methods with priority control#[BootMethod] - Boot phase methods with priority controlExample:
class DatabaseBootloader extends Bootloader
{
#[SingletonMethod]
#[BindScope('http')]
public function createConnection(): ConnectionInterface
{
return new Connection(...);
}
#[InitMethod(priority: 100)]
public function registerCoreServices(Container $container): void
{
// Register core services first
}
}
Benefits:
PR #1190 | Author: [@butschster](https://github.com/butschster)
Extends HTTP method support to include LINK and UNLINK verbs as specified in RFC 2068 and RFC 5988, allowing applications to handle specialized HTTP verbs for managing relationships between resources.
PR #1230 | Author: [@gam6itko](https://github.com/gam6itko)
Adds support for the Reply-To email header in the SendIt mailer component. Email messages can now properly set and handle Reply-To headers, allowing recipients to reply to a different address than the sender.
PR #1232 | Author: [@burn1ngbear](https://github.com/burn1ngbear)
Adds event hooks to the email rendering process for better extensibility:
Pre-render event: Triggered before email template renderingPost-render event: Triggered after email template renderingThis allows modification of email content before rendering and enables logging, validation, or transformation after rendering.
PR #1233 | Author: [@burn1ngbear](https://github.com/burn1ngbear)
Adds missing methods to the Reactor component for working with constant types, improving the code generation capabilities when managing constant type information in generated PHP code.
PR #1220 | Author: [@butschster](https://github.com/butschster)
Major performance optimization for the container's dependency resolution and scope handling:**
Invoker::invoke() to avoid unnecessary class instantiation when calling static functionsFactory::make() callsThe iterative scope resolution eliminates recursive overhead and reduces exception handling, resulting in significantly faster dependency resolution.
PR #1221 | Author: [@roxblnfk](https://github.com/roxblnfk)
Optimizes telemetry trace naming for queue job processing by removing job IDs from span names. This reduces cardinality and allows proper grouping of traces by job type in observability tools, resulting in better trace aggregation, easier performance pattern identification, and reduced storage overhead.
PR #1215 | Author: [@rauanmayemir](https://github.com/rauanmayemir)
Fixes a bug where route group prefixes were not being automatically applied to routes in the group. Previously, developers had to manually include the prefix in each route path or add it explicitly, which was counter-intuitive. Now group prefixes are automatically applied to all routes in the group as expected.
PR #1219 | Author: [@butschster](https://github.com/butschster) | Closes: #1217
Critical bug fix for the telemetry component that was preventing trace context from being added to log records. The tracer was not using the scoped proxy container, resulting in empty trace context. Fixed by ensuring TelemetryProcessor uses the scoped container properly.
PR #1212 | Author: [@rauanmayemir](https://github.com/rauanmayemir)
Fixes an issue where TracerInterface was overwriting trace context every time it was accessed, causing traces to be lost frequently. The solution binds current TracerInterface as singleton for consistent trace context within request scope and fixes scope handling in AbstractTracer::runScope method.
Benefits:
PR #1214 | Author: [@rauanmayemir](https://github.com/rauanmayemir)
static Return Type in Proxy GeneratorFixes a critical bug in the proxy class generator that caused failures when proxying interfaces with static return types. The generator now properly handles static return type, preventing failures in dependency injection and scoped bindings. Also adds resolving trace to resolver exceptions for better debugging.
PR #1222 | Author: [@roxblnfk](https://github.com/roxblnfk)
Fixes deprecation warnings when using Symfony Console 7.4+ and adds support for Symfony 8. Replaced deprecated add() method with addCommand() for Symfony 7.4+ while maintaining backward compatibility with Symfony 6.4. Updated version constraints to ^6.4.17 || ^7.2 || ^8.0.
PR #1240 | Author: [@gam6itko](https://github.com/gam6itko)
Corrects the return type annotation for the getDelay() method to match actual behavior and improve static analysis accuracy.
PR #1238 | Author: [@roxblnfk](https://github.com/roxblnfk)
nikic/php-parser v5PR #1205 | Author: [@msmakouz](https://github.com/msmakouz)
General maintenance update including dependency updates, code cleanup, and minor improvements across the framework.
PR #1236 | Author: [@roxblnfk](https://github.com/roxblnfk)
Full Changelog: https://github.com/spiral/framework/compare/3.15.0...3.16.0
A huge thank you to all contributors who made this release possible:
SpanInterface singleton rebinding in the Telemetry component (see https://github.com/spiral/app/issues/157)Full Changelog: https://github.com/spiral/framework/compare/3.15.7...3.15.8
static return type by @roxblnfk in https://github.com/spiral/framework/pull/1222
Also added resolving trace to Resolver exceptionsFull Changelog: https://github.com/spiral/framework/compare/3.15.6...3.15.7
Full Changelog: https://github.com/spiral/framework/compare/3.15.5...3.15.6
Bug Fixes
Full Changelog: https://github.com/spiral/framework/compare/3.15.0...3.15.6
Full Changelog: https://github.com/spiral/framework/compare/3.15.3...3.15.4
TracerInterface in the AbstractTracer::runScope method by @roxblnfk and @rauanmayemir in https://github.com/spiral/framework/pull/1214\Spiral\Core\Scope is public nowFull Changelog: https://github.com/spiral/framework/compare/3.15.2...3.15.3
Full Changelog: https://github.com/spiral/framework/compare/3.15.0...3.15.2
nikic/php-parser v5Full Changelog: https://github.com/spiral/framework/compare/3.15.0...3.15.1
AppEnvironment enum: added aliases for production and test environments (#1170).allowSingletonsRebinding to false right away.KeyWriting, CacheRetrieving, KeyDeleting and failed operations like KeyWriteFailed, KeyDeleteFailed (#1156).ServerRequestInterface object is now passed into the call context of interceptors (#1168)LazyPipeline (#1168)\Spiral\Http\Pipeline is deprecated now.UriHandler (#1192)$handler = $container->get(\Spiral\Router\UriHandler::class);
$handler->setStrict(true);
Optimized the telemetry component (#1168): it no longer opens a container scope each time in the AbstractTracer::runScope() method.
spiral/otel-bridge v1.2.2 has been released, which includes normalization of values for OTEL data types.
Changes in telemetry operation in the router:
http.response_content_length field is no longer filled.We are preparing to start the 4.x branch. This means it's time to "tidy up" the codebase: update all the code so that the difference between 3.x and 4.x is minimal. This way, fixes from 4.x can be easily applied to 3.x.
Pull requests using Rector from @samsonasik are very timely here:
setUp()/tearDown() method modifier protected on tests in #1202And Code Style improvements:
production and test environments by @roxblnfk in #1170Full Changelog: https://github.com/spiral/framework/compare/3.14.6...v3.15.0
What was changed
Bug Fixes
SpanInterface (#1206)@ inside a string that is not a directive (#1197)Full Changelog: https://github.com/spiral/framework/compare/3.14.9...3.14.10
http scope (#1176)Binder::bind method (#1195)Full Changelog: https://github.com/spiral/framework/compare/3.14.8...3.14.9
ArrayStorage::setMultiple() now returns true instead of false. By @MartkCz in https://github.com/spiral/framework/pull/1172Full Changelog: https://github.com/spiral/framework/compare/3.14.7...3.14.8
Full Changelog: https://github.com/spiral/framework/compare/3.14.6...3.14.7
Full Changelog: https://github.com/spiral/framework/compare/3.14.5...3.14.6
Full Changelog: https://github.com/spiral/framework/compare/3.14.4...3.14.5
DebugBootloader now uses a Factory Proxy to resolve collectors.
Unresolved collectors don't break state populating flow.Full Changelog: https://github.com/spiral/framework/compare/3.14.3...3.14.4
GuardScope to operate within Container Scopes by @roxblnfk in https://github.com/spiral/framework/pull/1146Full Changelog: https://github.com/spiral/framework/compare/3.14.2...3.14.3
Fixes:
Code quality:
Full Changelog: https://github.com/spiral/framework/compare/3.14.1...3.14.2
AbstractTarget by @roxblnfk in https://github.com/spiral/framework/pull/1132roadrunner-bridge < 3.7 and sapi-bridge < 1.1RemoveUselessParamTagRector and RemoveUselessReturnTagRector by @samsonasik in https://github.com/spiral/framework/pull/1130[!WARNING] If you are using
spiral/roadrunner-bridge, you need to update it to version^3.7or^4.0.
The HMVC package has been deprecated. It has been replaced by the new spiral/interceptors package, where we have reworked the interceptors. The basic principle remains the same, but the interface is now more understandable and convenient.
In the old CoreInterceptorInterface, the $controller and $action parameters caused confusion by creating a false association with HTTP controllers. However, interceptors are not tied to HTTP and are used universally. Now, instead of $controller and $action, we use the Target definition, which can point to more than just class methods.
The $parameters parameter, which is a list of arguments, has now been moved to the CallContextInterface invocation context.
/** [@deprecated](https://github.com/deprecated) Use InterceptorInterface instead */
interface CoreInterceptorInterface
{
public function process(string $controller, string $action, array $parameters, CoreInterface $core): mixed;
}
interface InterceptorInterface
{
public function intercept(CallContextInterface $context, HandlerInterface $handler): mixed;
}
The CallContextInterface invocation context contains all the information about the call:
[!NOTE]
CallContextInterfaceis immutable.
TargetInterface defines the target whose call we want to intercept.
If you need to replace the Target in the interceptor chain, use the static methods of the \Spiral\Interceptors\Context\Target class to create a new Target.
The basic set includes several types of Target:
Target::fromReflectionMethod(ReflectionFunctionAbstract $reflection, class-string|object $classOrObject) Target::fromReflectionFunction(\ReflectionFunction $reflection, array $path = []) Target::fromClosure(\Closure $closure, array $path = []) $target = Target::fromClosure($this->someAction(...));
Target::fromPathString(string $path, string $delimiter = '.') and
Target::fromPathArray(array $path, string $delimiter = '.') Target::fromPair(string|object $controller, string $action) Spiral 3.x will work as expected with both old and new interceptors. However, new interceptors should be created based on the new interface.
In Spiral 4.x, support for old interceptors will be disabled. You will likely be able to restore it by including the spiral/hmvc package.
If you need to manually build an interceptor chain, use \Spiral\Interceptors\PipelineBuilderInterface.
In Spiral v3, two implementations are provided:
\Spiral\Interceptors\PipelineBuilder — an implementation for new interceptors only.\Spiral\Core\CompatiblePipelineBuilder — an implementation from the spiral/hmvc package that supports both old and new interceptors simultaneously.[!NOTE] In Spiral 3.14, the implementation for
PipelineBuilderInterfaceis not defined in the container by default.CompatiblePipelineBuilderis used in Spiral v3 services as a fallback implementation. If you define your own implementation, it will be used instead of the fallback implementation in all framework pipelines.
At the end of the interceptor chain, there should always be a \Spiral\Interceptors\HandlerInterface, which will be called if the interceptor chain does not terminate with a result or exception.
The spiral/interceptors package provides several basic handlers:
\Spiral\Interceptors\Handler\CallableHandler — simply calls the callable from the Target "as is".\Spiral\Interceptors\Handler\AutowireHandler — calls a method or function, resolving missing arguments using the container.use Spiral\Core\CompatiblePipelineBuilder;
use Spiral\Interceptors\Context\CallContext;
use Spiral\Interceptors\Context\Target;
use Spiral\Interceptors\Handler\CallableHandler;
$interceptors = [
new MyInterceptor(),
new MySecondInterceptor(),
new MyThirdInterceptor(),
];
$pipeline = (new CompatiblePipelineBuilder())
->withInterceptors(...$interceptors)
->build(handler: new CallableHandler());
$pipeline->handle(new CallContext(
target: Target::fromPair($controller, $action),
arguments: $arguments,
attributes: $attributes,
));
Container Scopes are integrated even deeper. In Spiral, each type of worker is handled by a separate dispatcher. Each dispatcher has its own scope, which can be used to limit the set of services available to the worker.
During request processing, such as HTTP, the context (ServerRequestInterface) passes through a middleware pipeline.
At the very end, when the middleware has finished processing and the controller has not yet been called, there is a moment when the request context is finally prepared.
At this moment, the contextual container scope (in our case, http-request) is opened, and the ServerRequestInterface is placed in the container.
In this scope, interceptors come into play, after which the controller is executed.
As before, you can additionally open scopes in middleware, interceptors, or the business layer, for example, to limit the authorization context in a multi-tenant application.
You can view the names of dispatcher scopes and their contexts in the enum \Spiral\Framework\Spiral.
http.request scope added by @msmakouz in #1069ScopeName to Spiral by @msmakouz in #1078PaginationProviderInterface binding in scope by @msmakouz in #1079Proxy to the InvokerInterface in JobHandler by @msmakouz in #1091TracerFactoryInterface by @gam6itko in #1119Spiral\Queue\HandlerInterface::handle() by @gam6itko in #1120Full Changelog: https://github.com/spiral/framework/compare/3.13.0...3.14.1
LoggerChannel attributeWe are excited to introduce a new feature that enhances the flexibility of the logging component. With the new LoggerChannel attribute, developers can now specify the logger channel directly in the code.
Example Usage:
class SomeService
{
public function __construct(
// Logger with channel `roadrunner` will be injected
#[LoggerChannel('roadrunner')] public LoggerInterface $logger
){}
}
This feature allows for better organization and clarity in logging, helping you maintain and debug your application more efficiently.
by @roxblnfk in https://github.com/spiral/framework/pull/1102
With this update, you can now scan for attributes in parent classes, making your class discovery process more comprehensive and efficient.
Why This Matters
Previously, the tokenizer could only listen to classes where attributes were found. This limitation did not allow for the automatic (convenient) detection of classes by parent attributes and the effective use of the tokenizer cache. With this update, it will also listen to interfaces that the class with the attribute implements and the classes that it extends. This new feature leverages the full power of the tokenizer without the need to scan all classes and handle them manually, ensuring a more efficient and thorough attribute detection process.
Here is a practical example of how to use this feature:
use Spiral\Tokenizer\Attribute\TargetAttribute;
#[TargetAttribute(attribute: MyAttribute::class, scanParents: true)]
class MyListener implements TokenizationListenerInterface
{
public function listen(\ReflectionClass $class): void
{
// Your logic here
}
public function finalize(): void
{
// Your logic here
}
}
by @roxblnfk in https://github.com/spiral/framework/pull/1110
SyncDriver by @msmakouz in https://github.com/spiral/framework/pull/1107HEAD and OPTIONS HTTP methods to route:list command by @tairau in https://github.com/spiral/framework/pull/1109directive parser by @butschster in https://github.com/spiral/framework/pull/1098mixed type in the methods parameters by @msmakouz in https://github.com/spiral/framework/pull/1092StateBinder::hasInjector() method by @anoxia in https://github.com/spiral/framework/pull/1105TokenizerBootloader by @butschster in https://github.com/spiral/framework/pull/1093Full Changelog: https://github.com/spiral/framework/compare/3.12.0...3.13.0
spiral/coreAdvanced Context Handling in Injector Implementations by @roxblnfk in https://github.com/spiral/framework/pull/1041
This pull request presents a significant update to the injector system, focusing on the createInjection method of the Spiral\Core\Container\InjectorInterface. The key enhancement lies in the augmented ability of the injector to handle context more effectively.
Previously, the createInjection method accepted two parameters: the ReflectionClass object of the requested class and a context, which was limited to being either a string or null. This approach, while functional, offered limited flexibility in dynamically resolving dependencies based on the calling context.
The updated createInjection method can now accept an extended range of context types including Stringable|string|null, mixed, or ReflectionParameter|string|null. This broadening allows the injector to receive more detailed contextual information, enhancing its capability to make more informed decisions about which implementation to provide.
Now you can do something like this:
<?php
declare(strict_types=1);
namespace App\Application;
final class SomeService
{
public function __construct(
#[DatabaseDriver(name: 'mysql')]
public DatabaseInterface $database,
#[DatabaseDriver(name: 'sqlite')]
public DatabaseInterface $database1,
) {
}
}
And example of injector
<?php
declare(strict_types=1);
namespace App\Application;
use Spiral\Core\Container\InjectorInterface;
final class DatabaseInjector implements InjectorInterface
{
public function createInjection(\ReflectionClass $class, \ReflectionParameter|null|string $context = null): object
{
$driver = $context?->getAttributes(DatabaseDriver::class)[0]?->newInstance()?->name ?? 'mysql';
return match ($driver) {
'sqlite' => new Sqlite(),
'mysql' => new Mysql(),
default => throw new \InvalidArgumentException('Invalid database driver'),
};
}
}
Add non-reportable exceptions by @msmakouz in https://github.com/spiral/framework/pull/1044
The ability to exclude reporting of certain exceptions has been added. By default, Spiral\Http\Exception\ClientException, Spiral\Filters\Exception\ValidationException, and Spiral\Filters\Exception\AuthorizationException are ignored.
Exceptions can be excluded from the report in several different ways:
NonReportableTo exclude an exception from the report, you need to add the Spiral\Exceptions\Attribute\NonReportable attribute to the exception class.
use Spiral\Exceptions\Attribute\NonReportable;
#[NonReportable]
class AccessDeniedException extends \Exception
{
// ...
}
dontReportInvoke the dontReport method in the Spiral\Exceptions\ExceptionHandler class. This can be done using the bootloader.
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Exceptions\ExceptionHandler;
final class AppBootloader extends Bootloader
{
public function init(ExceptionHandler $handler): void
{
$handler->dontReport(EntityNotFoundException::class);
}
}
nonReportableExceptionsYou can override the nonReportableExceptions property with predefined exceptions.
This release marks a foundational shift in how we approach dependency management within our framework, setting the stage for the upcoming version 4.0. With these changes, we're not just tweaking the system; we're laying down the groundwork for more robust, efficient, and intuitive handling of dependencies in the long run. To ensure everyone can make the most out of these updates, we will be rolling out a series of tutorials aimed at helping you navigate through the new features and enhancements.
The context is also extended on other container methods get() (see https://github.com/spiral/framework/pull/1041)
If the container scope is not open, it is assumed by default that dependencies are resolved in the scope named root. Now when calling invoke(), make(), get(), the container will globally register itself with the root scope if no other scope was opened. Before this, the container resolved dependencies as if outside the scope.
The experimental ContainerScopeInterface has been removed. The method getBinder(?string $scope = null): BinderInterface has been moved to BinderInterface at the annotation level.
The Container::runScoped() method (in the implementation) was additionally marked as [@deprecated](https://github.com/deprecated) and will be removed when its use in tests is reduced to zero. Instead of the Container::runScoped(), you should now call the old Container::runScope(), but with passing the DTO Spiral\Core\Scope instead of the list of bindings.
$container->runScope(
new Scope(name: 'auth', bindings: ['actor' => new Actor()]),
function(ContainerInterface $container) {
dump($container->get('actor'));
},
);
Instead of the now removed ContainerScopeInterface::getCurrentContainer() method, the user is offered another way to get dependencies from the container of the current scope - a proxy.
The user can mark the dependency with a new attribute Spiral\Core\Attribute\Proxy.
Warning: The dependency must be defined by an interface.
When resolving dependencies, the container will create a proxy object that implements the specified interface. When calling the interface method, the proxy object will get the container of the current scope, request the dependency from it using its interface, and start the necessary method.
final class Service
{
public function __construct(
#[Proxy] public LoggerInterface $logger,
) {
}
public function doAction() {
// Equals to
// $container->getCurrentContainer()->get(LoggerInterface::class)->log('foo')
$this->logger->log('foo');
}
}
Important nuances:
// class
function __construct(
#[Proxy] private Dependency $dep,
#[Proxy] private ContainerInterface $container,
) {}
function handle() {
// There are four calls to the container under the hood.
$this->dep->foo();
$this->dep->bar();
$this->dep->baz();
$this->dep->red();
// Only two calls to the container and caching the value in a variable
// The first call - getting the container through the proxy
// The second - explicit retrieval of the dependency from the container
$dep = $this->container->get(Dependency::class);
$dep->foo();
$dep->bar();
$dep->baz();
$dep->red();
}
Added the ability to bind an interface as a proxy using the Spiral\Core\Config\Proxy configuration. This is useful in cases where a service needs to be used within a specific scope but must be accessible within the container for other services in root or other scopes (so that a service requiring the dependency can be successfully created and used when needed in the correct scope).
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Core\BinderInterface;
use Spiral\Core\Config\Proxy;
use Spiral\Framework\ScopeName;
use Spiral\Http\PaginationFactory;
use Spiral\Pagination\PaginationProviderInterface;
final class PaginationBootloader extends Bootloader
{
public function __construct(
private readonly BinderInterface $binder,
) {
}
public function defineSingletons(): array
{
$this->binder
->getBinder(ScopeName::Http)
->bindSingleton(PaginationProviderInterface::class, PaginationFactory::class);
$this->binder->bind(
PaginationProviderInterface::class,
new Proxy(PaginationProviderInterface::class, true) // <-------
);
return [];
}
}
Similar to Proxy, but also allows outputting a deprecation message when attempting to retrieve a dependency from the container. In the example below, we use two bindings, one in scope and one out of scope with Spiral\Core\Config\DeprecationProxy. When requesting the interface in scope, we will receive the service, and when requesting it out of scope, we will receive the service and a deprecation message.
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Core\BinderInterface;
use Spiral\Core\Config\DeprecationProxy;
use Spiral\Framework\ScopeName;
use Spiral\Http\PaginationFactory;
use Spiral\Pagination\PaginationProviderInterface;
final class PaginationBootloader extends Bootloader
{
public function __construct(
private readonly BinderInterface $binder,
) {
}
public function defineSingletons(): array
{
$this->binder
->getBinder(ScopeName::Http)
->bindSingleton(PaginationProviderInterface::class, PaginationFactory::class);
$this->binder->bind(
PaginationProviderInterface::class,
new DeprecationProxy(PaginationProviderInterface::class, true, ScopeName::Http, '4.0') // <----------
);
return [];
}
}
Added the ability to specify the scope name for the dispatcher using the Spiral\Attribute\DispatcherScope attribute.
use Spiral\Attribute\DispatcherScope;
use Spiral\Boot\DispatcherInterface;
#[DispatcherScope(scope: 'console')]
final class ConsoleDispatcher implements DispatcherInterface
{
// ...
}
The registration of dispatchers has been changed. The accepted type in the addDispatcher method of the Spiral\Boot\AbstractKernel class has been extended from DispatcherInterface to string|DispatcherInterface. Before these changes, the method accepted a created DispatcherInterface object, now it can accept a class name string or an object. In version 4.0, the DispatcherInterface type will be removed. When passing an object, only its class name will be saved. And when using the dispatcher, its object will be created anew.
Example with ConsoleDispatcher:
public function init(AbstractKernel $kernel): void
{
$kernel->bootstrapped(static function (AbstractKernel $kernel): void {
$kernel->addDispatcher(ConsoleDispatcher::class);
});
}
The dispatchers are now created in their own scope and receive dependencies that are specified in this scope. But due to the need to check whether the dispatcher can handle the request or not before creating the dispatcher object, the canServe method in dispatchers must be static:
public static function canServe(EnvironmentInterface $env): bool
{
return (PHP_SAPI === 'cli' && $env->get('RR_MODE') === null);
}
This method has been removed from the Spiral\Boot\DispatcherInterface, for backward compatibility it can be non-static, as it was before (then an object will be created for its call) or static and accept Spiral\Boot\EnvironmentInterface.
scaffolder:info console commandAdds
scaffolder:infoconsole command by @butschster in https://github.com/spiral/framework/pull/1068
Now you can list available commands.
monolog/monolog v3.x by @msmakouz in https://github.com/spiral/framework/pull/1049cocur/slugify 4.x by @msmakouz in https://github.com/spiral/framework/pull/1048league/flysystem v3.x by @msmakouz in https://github.com/spiral/framework/pull/1050doctrine/annotations by @msmakouz in https://github.com/spiral/framework/pull/1059spiral/translator Added a check for the existence of a message by @msmakouz in https://github.com/spiral/framework/pull/1062spiral/translator Fixed getLocaleDirectory method by @msmakouz in https://github.com/spiral/framework/pull/1075spiral/core Fixed check for the existence of a binding in parent scopes using Conainer::has by @msmakouz in https://github.com/spiral/framework/pull/1065spiral/router Updated route:list command for customization route class by @iAvenger01 in https://github.com/spiral/framework/pull/1070spiral/core Added the ability to configure container via options by @msmakouz in https://github.com/spiral/framework/pull/1082Full Changelog: https://github.com/spiral/framework/compare/3.11.1...3.12.0
tokenizer:Info console command by @msmakouz in https://github.com/spiral/framework/pull/1042Full Changelog: https://github.com/spiral/framework/compare/3.11.0...3.11.1
Spiral\Debug\Config\DebugConfig by @msmakouz in https://github.com/spiral/framework/pull/1026booting event by @msmakouz in https://github.com/spiral/framework/pull/1033runScope() by @roxblnfk in https://github.com/spiral/framework/pull/1038Full Changelog: https://github.com/spiral/framework/compare/3.10.1...3.11.0
namedArguments parameter to the TargetAttribute by @msmakouz in https://github.com/spiral/framework/pull/1018Full Changelog: https://github.com/spiral/framework/compare/3.10.0...3.10.1
We've introduced a new interface, Spiral\Boot\Bootloader\BootloaderRegistryInterface, and its implementation, Spiral\Boot\Bootloader\BootloaderRegistry. This update makes the process of registering bootloaders in Spiral much simpler and more flexible.
Now, you can easily manage your bootloaders using our spiral-packages/discoverer package. This package helps you automatically find and register bootloaders specified in your composer.json like in example below:
{
// ...
"extra": {
"spiral": {
"bootloaders": [
"Spiral\\Monolog\\Bootloader\\DotenvBootloader",
"Spiral\\DotEnv\\Bootloader\\MonologBootloader"
],
"dont-discover": [
"spiral-packages/event-bus"
]
}
}
}
This feature also allows for bootloader discovery from various sources, such as configuration files or other custom methods.
by @msmakouz in https://github.com/spiral/framework/pull/1015
The spiral/filters package in Spiral's ecosystem is designed for filtering and, optionally, validating input data. It enables you to set specific rules for each input field, ensuring that the data received matches the expected format and other defined criteria.
For example, consider this filter:
namespace App\Endpoint\Web\Filter;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
final class UserFilter extends Filter
{
#[Query(key: 'username')]
public string $username;
}
In this scenario, the username is expected to be a string. However, there might be instances where the input data is of the wrong type, such as an array or an integer. Previously, such mismatches would result in an exception being thrown by the application.
With the new update, we've added the capability to specify custom error messages for these mismatches. This enhancement allows for more graceful handling of incorrect data types. Here's how you can implement it:
namespace App\Endpoint\Web\Filter;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Attribute\CastingErrorMessage;
final class UserFilter extends Filter
{
#[Query(key: 'username')]
#[CastingErrorMessage('Invalid type')]
public string $username;
}
This update ensures that your application can provide clearer feedback when encountering data of an unexpected type.
by @msmakouz in https://github.com/spiral/framework/pull/1016
There is a new DTO class Spiral\Boot\Attribute\BootloadConfig which enables the inclusion or exclusion of bootloaders, passing parameters that will be forwarded to the init and boot methods of the bootloader, and dynamically adjusting the bootloader loading based on environment variables.
Here is a simple example:
namespace App\Application;
use Spiral\Boot\Attribute\BootloadConfig;
use Spiral\Prototype\Bootloader\PrototypeBootloader;
class Kernel extends \Spiral\Framework\Kernel
{
// ...
public function defineBootloaders(): array
{
return [
// ...
PrototypeBootloader::class => new BootloadConfig(allowEnv: ['APP_ENV' => ['local', 'dev']]),
// ...
];
}
// ...
}
In this example, we specified that the PrototypeBootloader should be loaded only if the environment variable APP_ENV is defined and has a value of local or dev.
You can also define a function that returns a BootloadConfig object. This function can take arguments, which might be obtained from the container.
PrototypeBootloader::class => static fn (AppEnvironment $env) => new BootloadConfig(enabled: $env->isLocal()),
You can also use BootloadConfig class as an attribute to control how a bootloader behaves.
use Spiral\Boot\Attribute\BootloadConfig;
use Spiral\Boot\Bootloader\Bootloader;
#[BootloadConfig(allowEnv: ['APP_ENV' => 'local'])]
final class SomeBootloader extends Bootloader
{
}
Attributes are a great choice when you want to keep the configuration close to the bootloader's code. It's a more intuitive way to set up bootloaders, especially in cases where the configuration is straightforward and doesn't require complex logic.
By extending BootloadConfig, you can create custom classes that encapsulate specific conditions under which bootloaders should operate.
Here's an example
class TargetRRWorker extends BootloadConfig {
public function __construct(array $modes)
{
parent::__construct(
env: ['RR_MODE' => $modes],
);
}
}
// ...
class Kernel extends Kernel
{
public function defineBootloaders(): array
{
return [
HttpBootloader::class => new TargetRRWorker(['http']),
RoutesBootloader::class => new TargetRRWorker(['http']),
// Other bootloaders...
];
}
}
by @msmakouz in https://github.com/spiral/framework/pull/1017
Full Changelog: https://github.com/spiral/framework/compare/3.9.1...3.10
RetryPolicyInterceptor for Queue componentAdded Spiral\Queue\Interceptor\Consume\RetryPolicyInterceptor to enable automatic job retries with a configurable retry policy. To use it, need to add the Spiral\Queue\Attribute\RetryPolicy attribute to the job class:
use Spiral\Queue\Attribute\RetryPolicy;
use Spiral\Queue\JobHandler;
#[RetryPolicy(maxAttempts: 3, delay: 5, multiplier: 2)]
final class Ping extends JobHandler
{
public function invoke(array $payload): void
{
// ...
}
}
Create an exception that implements interface Spiral\Queue\Exception\RetryableExceptionInterface:
use Spiral\Queue\Exception\RetryableExceptionInterface;
use Spiral\Queue\RetryPolicyInterface;
class RetryException extends \DomainException implements RetryableExceptionInterface
{
public function isRetryable(): bool
{
return true;
}
public function getRetryPolicy(): ?RetryPolicyInterface
{
return null;
}
}
The exception must implement the two methods isRetryable and getRetryPolicy. These methods can override the retry behavior and cancel the re-queue- or change the retry policy.
If a RetryException is thrown while a job runs, the job will be re-queued according to the retry policy.
Pull request: https://github.com/spiral/framework/pull/980 by @msmakouz
Added ability to configure serializer and job type using attributes.
use App\Domain\User\Entity\User;
use Spiral\Queue\Attribute\Serializer;
use Spiral\Queue\Attribute\JobHandler as Handler;
use Spiral\Queue\JobHandler;
#[Handler('ping')]
#[Serializer('marshaller-json')]
final class Ping extends JobHandler
{
public function invoke(User $payload): void
{
// ...
}
}
Pull request: https://github.com/spiral/framework/pull/990 by @msmakouz
Now you can configure the Monolog messages format via environment variable MONOLOG_FORMAT.
MONOLOG_FORMAT="[%datetime%] %level_name%: %message% %context%\n"
Pull request: https://github.com/spiral/framework/pull/994 by @msmakouz
Now you can register additional directories with translation files for the Translator component. This can be useful when developing additional packages for the Spiral Framework, where the package may provide translation files (for example, validators). Translation files in an application can override translations from additional directories.
A directory with translations can be registered via the Spiral\Bootloader\I18nBootloader bootloader or translator.php configuration file.
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Bootloader\I18nBootloader;
final class AppBootloader extends Bootloader
{
public function init(I18nBootloader $i18n): void
{
$i18n->addDirectory('some/directory');
}
}
return [
// ...
'directories' => [
'some/directory'
],
// ...
];
Pull request: https://github.com/spiral/framework/pull/996 by @msmakouz
Storage componentHave you ever faced challenges in storing your app's exception snapshots when working with stateless applications? We've got some good news. With our latest update, we've made it super easy for you.
By integrating with the spiral/storage component, we're giving your stateless apps the power to save exception snapshots straight into S3.
Spiral\Bootloader\SnapshotsBootloader with Spiral\Bootloader\StorageSnapshotsBootloader.SNAPSHOTS_BUCKET environment variable.app/src/Application/Bootloader/ExceptionHandlerBootloader.php to replace the exception reporter Spiral\Exceptions\Reporter\FileReporter with Spiral\Exceptions\Reporter\StorageReporter in the boot method (an example for a default installation of spiral/app).Pull request: https://github.com/spiral/framework/pull/986 by @msmakouz
prototype:list console command for listing prototype dependenciesThe prototype:list command is a super cool addition to our Spiral Framework. It helps developers by providing an easy way to list all the classes registered in the Spiral\Prototype\PrototypeRegistry. These registered classes are essential for project prototyping.
Using the command is simple. Just run the following line in your terminal:
php app.php prototype:list
Once you do that, you'll get a neat table that displays all the registered prototypes, including their names and target classes. This makes it incredibly easy to see what's available for your project prototyping needs.
+------------------+-------------------------------------------------------+
| Name: | Target: |
+------------------+-------------------------------------------------------+
| app | App\Application\Kernel |
| classLocator | Spiral\Tokenizer\ClassesInterface |
| console | Spiral\Console\Console |
| broadcast | Spiral\Broadcasting\BroadcastInterface |
| container | Psr\Container\ContainerInterface |
| encrypter | Spiral\Encrypter\EncrypterInterface |
| env | Spiral\Boot\EnvironmentInterface |
| files | Spiral\Files\FilesInterface |
| guard | Spiral\Security\GuardInterface |
| http | Spiral\Http\Http |
| i18n | Spiral\Translator\TranslatorInterface |
| input | Spiral\Http\Request\InputManager |
| session | Spiral\Session\SessionScope |
| cookies | Spiral\Cookies\CookieManager |
| logger | Psr\Log\LoggerInterface |
| logs | Spiral\Logger\LogsInterface |
| memory | Spiral\Boot\MemoryInterface |
| paginators | Spiral\Pagination\PaginationProviderInterface |
| queue | Spiral\Queue\QueueInterface |
| queueManager | Spiral\Queue\QueueConnectionProviderInterface |
| request | Spiral\Http\Request\InputManager |
| response | Spiral\Http\ResponseWrapper |
| router | Spiral\Router\RouterInterface |
| snapshots | Spiral\Snapshots\SnapshotterInterface |
| storage | Spiral\Storage\BucketInterface |
| serializer | Spiral\Serializer\SerializerManager |
| validator | Spiral\Validation\ValidationInterface |
| views | Spiral\Views\ViewsInterface |
| auth | Spiral\Auth\AuthScope |
| authTokens | Spiral\Auth\TokenStorageInterface |
| cache | Psr\SimpleCache\CacheInterface |
| cacheManager | Spiral\Cache\CacheStorageProviderInterface |
| exceptionHandler | Spiral\Exceptions\ExceptionHandlerInterface |
| users | App\Infrastructure\Persistence\CycleORMUserRepository |
+------------------+-------------------------------------------------------+
This new feature enhances developer productivity and ensures that we're making the most of the Spiral Framework's capabilities. It provides clarity on available prototypes, which can be crucial when building and extending our projects.
Note You might notice that we've also renamed the old
prototype:listcommand toprototype:usageto better align with its purpose.
Pull request: https://github.com/spiral/framework/pull/1003 by @msmakouz
array to mixed by @msmakouz in https://github.com/spiral/framework/pull/992bubble as true by default in logRotate method by @msmakouz in https://github.com/spiral/framework/pull/997Full Changelog: https://github.com/spiral/framework/compare/3.8.4...3.9.0
visibility in the Storage configuration by @msmakouz in https://github.com/spiral/framework/pull/977Tokenizer Info console command by @msmakouz in https://github.com/spiral/framework/pull/979null instead of using unset in the reset method by @msmakouz in https://github.com/spiral/framework/pull/985hasInstance in the parent scope by @msmakouz in https://github.com/spiral/framework/pull/981Full Changelog: https://github.com/spiral/framework/compare/3.8.3...3.8.4
singletons in the hasInstance method by @msmakouz in https://github.com/spiral/framework/pull/975Full Changelog: https://github.com/spiral/framework/compare/3.8.2...3.8.3
How can I help you explore Laravel packages today?