Releases: spiral/framework
v3.8.1
What's Changed
- Adding the ability to configure the Attributes cache or disable the cache by @msmakouz in #968
- Fixes Event Dispatcher rebinding by @butschster in #970
- Fixes incorrect Concatenation of Route Pattern with Prefix in Route Group by @butschster in #967
- Fixes loading ENV variables from dotenv in Kernel System section by @butschster in #972
Full Changelog: 3.8.0...3.8.1
v3.8.0
Improvements
1. Added instructions feature for scaffold generator
We are excited to announce a new feature, that enhances the scaffold generation process by providing clear and concise instructions on the next steps to be taken after generating various classes for your application.
With the Instructions feature, you can generate classes for the following components:
- Bootloader
- Command
- Config
- Controller
- Request Filter
- Job Handler
- Middleware
Each generated class comes with a set of instructions on what to do next, ensuring a smooth and hassle-free development experience.
Let's take a look at an example:
php app.php create:command ScanSite
Will display
Declaration of 'ScanSiteCommand' has been successfully written into 'app/src/Endpoint/Console/ScanSiteCommand.php'.
Next steps:
1. Use the following command to run your command: 'php app.php scan:site'
2. Read more about user Commands in the documentation: https://spiral.dev/docs/console-commands
To experience the Instructions feature, simply use the scaffold generator commands for the respective components and follow the provided instructions for each generated class. The documentation links accompanying the instructions serve as a valuable resource for in-depth explanations and best practices.
By @butschster in #945
2. Adds the ability to specify a custom directory for specific declaration types in the [spiral/scaffolder] component
TheΒ spiral/scaffolder
Β component provides the ability to generate various classes, such as bootloaders, HTTP controllers, console commands, request filters, and Cycle ORM entities. These generated classes are stored in a directory specified in the configuration file. However, there was no way to specify a custom directory for specific declaration types.
This PR added a new configuration option to the declarations array that allows for the specification of a custom directory for a specific declaration type where to store a generated file.
UsingΒ directory
Β in theΒ ScaffolderBootloader::addDeclaration
Β method:
class ScaffolderBootloader extends Bootloader
{
//...
public function boot(BaseScaffolderBootloader $scaffolder, DatabaseSeederConfig $config): void
{
$scaffolder->addDeclaration(Declaration\FactoryDeclaration::TYPE, [
'namespace' => $config->getFactoriesNamespace(),
'postfix' => 'Factory',
'class' => Declaration\FactoryDeclaration::class,
'baseNamespace' => 'Database',
'directory' => $config->getFactoriesDirectory() // <=============
]);
$scaffolder->addDeclaration(Declaration\SeederDeclaration::TYPE, [
'namespace' => $config->getSeedersNamespace(),
'postfix' => 'Seeder',
'class' => Declaration\SeederDeclaration::class,
'baseNamespace' => 'Database',
'directory' => $config->getSeedersDirectory() // <=============
]);
}
}
Via configuration fileΒ app/config/scaffolder.php
return [
'directory' => directory('app') . 'src/',
'declarations' => [
Declaration\BootloaderDeclaration::TYPE => [
'namespace' => 'Application\Bootloader',
'postfix' => 'Bootloader',
'class' => Declaration\BootloaderDeclaration::class,
'directory' => directpry('app') . '/Infrastructure/Bootloader' // <=============
],
],
];
This allows users to customize the directory structure of their generated classes more easily, and improves the overall flexibility of the scaffolder component.
3. Filters improvements
Value casters for Filter properties
Introducing Spiral\Filters\Model\Mapper\Mapper
that sets values for filter properties. It utilizes a collection of casters, each designed to handle a specific type of value.
Default casters include:
Spiral\Filters\Model\Mapper\EnumCaster
Allows the use ofΒ PHP enumsΒ in a filter properties. This caster verifies the property type as an enum and creates the necessary enumeration from the value passed to the filter.
<?php
declare(strict_types=1);
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Model\Filter;
use App\User\Type;
final class RegisterUser extends Filter
{
#[Post]
public string $name;
#[Post]
public Type $status = Type::Admin;
// ...
}
Spiral\Filters\Model\Mapper\UuidCaster
Allows the use ofΒ ramsey/uuidΒ in a filter properties. This caster confirms the property type as UUID and constructs an UUID object from the string provided to the Filter.
<?php
declare(strict_types=1);
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Model\Filter;
use Ramsey\Uuid\UuidInterface;
final class UpdateUser extends Filter
{
#[Post]
public UuidInterface $uuid;
// ...
}
Spiral\Filters\Model\Mapper\DefaultCaster
: Used when other casters are unable to assign a value. It sets the property value without any transformations.
Custom casters
Casters are extensible and can be created and added by users within the application. To achieve this, it's necessary to create a custom caster object, implement theΒ Spiral\Filters\Model\Mapper\CasterInterface
Β interface.
Let's take a look at an example:
<?php
declare(strict_types=1);
namespace App\Application\Filter\Caster;
use Spiral\Filters\Model\Mapper\CasterInterface;
final class BooleanCaster implements CasterInterface
{
public function supports(\ReflectionNamedType $type): bool
{
return $type->isBuiltin() && $type->getName() === 'bool';
}
public function setValue(FilterInterface $filter, \ReflectionProperty $property, mixed $value): void
{
$property->setValue($filter, (bool) $value);
}
}
And register it in theΒ Spiral\Filters\Model\Mapper\CasterRegistryInterface
Β viaΒ registerΒ method.
<?php
declare(strict_types=1);
namespace App\Application\Bootloader;
use Spiral\Filters\Model\Mapper\CasterRegistryInterface;
use Spiral\Boot\Bootloader\Bootloader;
use App\Application\Filter\Caster\BooleanCaster;
final class FilerBootloader extends Bootloader
{
public function init(CasterRegistryInterface $registry)
{
$registr->register(new BooleanCaster());
}
}
4. Added TokenStorageScope
This class can be used to access the specific implementation of the token storage that is used in the current container scope.
use Spiral\Auth\TokenStorageScope;
final class SomeController
{
public function index(TokenStorageScope $tokenStorage): string
{
$tokenStorage->load('some-id');
$tokenStorage->create(['id' => 'some-id']);
$tokenStorage->delete($token);
}
}
5. Container improvements
In this update, our primary focus has been on elevating the overall performance of the container
- We have successfully migrated a significant portion of the internal operations from runtime to configuration time.
- Furthermore, we've replaced the previous array-based structure that was utilized to store information about bindings within the container. The new approach involves the utilization of Data Transfer Objects (DTOs), which not only provides a more structured and organized manner of storing binding information but also offers developers a seamless way to configure container bindings using these objects.
These changes together make the container work faster and more efficiently while also using less memory. As a result, your applications should perform better overall.
Additionally,
To demonstrate the enhanced container functionality, here's an example showcasing the new binding configuration:
use Spiral\Core\Config\Factory;
$container->bind(LoggerInterface::class, new Factory(
callable: static function() {
return new Logger(....);
},
singleton: true
))
Read more about new binding here
Scopes
We also added a new container scope interface Spiral\Core\ContainerScopeInterface
that can be used to run code withing isolated IoC scope.
Note
Use it instead ofSpiral\Core\ScopeInterface
, which is deprecated now.
A new interface provides also an ability to bind aliases in a specific scope
use Spiral\Core\ScopeInterface;
final class AppBootloader extends Bootloader
{
public function init(ContainerScopeInterface $container)
{
// Bind into a specific scope
$container->getBinder('http')->bind(...);
// Bind into the current scope
$container->getBinder()->bind(...);
}
}
by @roxblnfk in #941 #935 #936
Singletons
In this release, we've introduced a robust safeguard to prevent accidental overwriting of existing singletons within the container. This new feature ensures a more controlled environment for managing your application's dependencies and provides greater clarity when redefining singletons. In previous versions of the container, users could override existing singleton definitions by redefining them with new configurations. While this flexibility allowed for dynamic changes, it also led to potential confusion and unexpected behavior. To ad...
v3.7.1
What's Changed
- Fixing PromotedParameter in the Reactor by @msmakouz in #922
- Fixed InputScope to allow retrieval of non-bag input sources by @butschster in #926
Full Changelog: 3.7.0...3.7.1
v3.7.0
New Features
1. Added the ability to push objects to a queue
Previously, only arrays could be pushed to the queue, but with this feature, developers can now push any other types, such as objects, strings, etc. Now you can use various serializers like Symfony Serializer, Valinor, and Protobuf to serialize and deserialize objects. For example, using a more efficient and compact serialization format like Protobuf can help reduce the size of the data being pushed to the queue, resulting in faster processing times and lower storage costs.
Note
Read more about jobs payload serialization on official documentation
2. Added the ability to guess option mode in by property type console commands
This makes it easier for developers to define command options by allowing them to simply define the property type and the console command will automatically guess the appropriate mode for the option.
namespace App\Endpoint\Console;
use Spiral\Console\Attribute\AsCommand;
use Spiral\Console\Attribute\Option;
use Spiral\Console\Command;
#[AsCommand(name: 'create:user', description: 'Create a new user')]
class CreateUserCommand extends Command
{
#[Option]
private bool $active; // InputOption::VALUE_NEGATABLE
#[Option]
private int $age; // InputOption::VALUE_REQUIRED
#[Option]
private int $friends = 0; // InputOption::VALUE_OPTIONAL (property have a default value)
#[Option]
private ?string $address = null; // InputOption::VALUE_OPTIONAL (nullable property)
#[Option]
private array $groups; // InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY
#[Option]
private array $phones = []; // InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY
#[Option]
private ?array $emails; // InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY
public function __invoke(): int
{
// ...
}
}
Note
Read more about console commands on official documentation
3. Added the ability to override Scaffolder config partially
Previously, if a developer wanted to customize the scaffolder configuration, they had to override the entire default configuration in the scaffolder.php
config file.
With this feature, developers can configure only specific options for each declaration type.
return [
'namespace' => 'App',
'declarations' => [
Declaration\BootloaderDeclaration::TYPE => [
'namespace' => 'Application\Bootloader',
],
Declaration\ConfigDeclaration::TYPE => [
'namespace' => 'Application\Config',
],
Declaration\ControllerDeclaration::TYPE => [
'namespace' => 'Endpoint\Web',
],
Declaration\MiddlewareDeclaration::TYPE => [
'class' => Declaration\MiddlewareDeclaration::class,
'namespace' => 'Application\Http\Middleware',
],
Declaration\CommandDeclaration::TYPE => [
'namespace' => 'Endpoint\Console',
],
Declaration\JobHandlerDeclaration::TYPE => [
'namespace' => 'Endpoint\Job',
'postfix' => 'Job',
],
],
];
It allows developers to customize only the specific declaration types they need, without having to override the entire default declaration configuration. This can make the configuration process simpler and reduce the risk of errors.
Note
Read more about scaffolding on official documentation
4. Refactored scaffolder commands
create:bootloader
command now has an ability to create a domain bootloader with interceptors using the -d option.create:command
- command has been improved with several changes, such as adding the final keyword for class and using PHP attributes for command definition and console command declaration. The command now also has the ability to add arguments and options to the generated console command.
// Generate command with arguments and options
php app.php create:command UserRegister -a username -a password -o isAdmin -d "Register a new user"
Will generate
<?php
declare(strict_types=1);
namespace App\Api\Cli\Command;
use Spiral\Console\Attribute\Argument;
use Spiral\Console\Attribute\AsCommand;
use Spiral\Console\Attribute\Option;
use Spiral\Console\Attribute\Question;
use Spiral\Console\Command;
#[AsCommand(name: 'user:register', description: 'Register a new user')]
final class UserRegisterCommand extends Command
{
#[Argument(description: 'Argument description')]
#[Question(question: 'What would you like to name the username argument?')]
private string $username;
#[Argument(description: 'Argument description')]
#[Question(question: 'What would you like to name the password argument?')]
private string $password;
#[Option(description: 'Argument description')]
private bool $isAdmin;
public function __invoke(): int
{
// Put your command logic here
$this->info('Command logic is not implemented yet');
return self::SUCCESS;
}
}
create:filter
command has also been added for filters declaration, which allows developers to generate filters with validation rules.
php app.php create:filter CreateUser -p username:post -p tags:post:array -p ip:ip -p token:header -p status:query:int
Will generate
<?php
declare(strict_types=1);
namespace App\Api\Web\Filter;
use Spiral\Filters\Attribute\Input\Header;
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Attribute\Input\RemoteAddress;
use Spiral\Filters\Model\Filter;
final class CreateUserFilter extends Filter
{
#[Post(key: 'username')]
public string $username;
#[Post(key: 'tags')]
public array $tags;
#[RemoteAddress(key: 'ip')]
public string $ip;
#[Header(key: 'token')]
public string $token;
#[Query(key: 'status')]
public int $status;
}
There is also an ability to use validator to validate filter
php app.php create:filter CreateUser -p ... -s
Will generate
<?php
declare(strict_types=1);
namespace App\Api\Web\Filter;
use Spiral\Filters\Attribute\Input\Header;
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Attribute\Input\Query;
use Spiral\Filters\Attribute\Input\RemoteAddress;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
use Spiral\Validator\FilterDefinition;
final class CreateUserFilter extends Filter implements HasFilterDefinition
{
#[Post(key: 'username')]
public string $username;
#[Post(key: 'tags')]
public array $tags;
#[RemoteAddress(key: 'ip')]
public string $ip;
#[Header(key: 'token')]
public string $token;
#[Query(key: 'status')]
public int $status;
public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition(validationRules: [
// Put your validation rules here
]);
}
}
All these improvements can help to streamline the process of generating classes using the scaffolder component, making it easier and more efficient for developers to generate the necessary classes for their application.
Note
Read more about scaffolding on official documentation
by @butschster in #902
Improvements
- Updated Psalm up to v5 by @msmakouz in #906
- Added support for
doctrine/annotations:2.x
package by @msmakouz in #897
Bug Fixes
- Fixed problem with displaying console commands description when it was defined via
DESCRIPTION
constant by @msmakouz in #889 - Fixed applying setters during validation filter objects by @msmakouz in #891
- Fixed the problem with using named parameters in class located by a tokenizer by @butschster in #895
- Fixed elapsed time calculation for LogTracer in spiral/telemetry component by @gam6itko in #919
v3.6.1
What's Changed
- Fixed Split Monorepo action by @msmakouz in #883
- Fixed scaffolder command
namespace
andcomment
options by @butschster in #884
Full Changelog: 3.6.0...3.6.1
v3.6.0
Spiral Framework 3.6 has been released, introducing a range of new features, performance improvements, and bug fixes.
What's Changed
1. Container
One notable change is the introduction of a new and improved container, which offers isolated memory scopes for more fine-grained control over how dependencies are resolved. This is useful in situations where you want to ensure that certain dependencies are only used within a specific context and do not bleed over to other parts of the application.
PR: #870
Container scopes
The Container::scope()
method is used to run a true isolated scope, and it takes a callable as its first argument. The callable is executed within the scope, and any bindings passed as the second argument will only be used within that scope.
Named scopes can also be used. This is useful for cases where you have multiple scopes with similar dependencies, but with some variations. Parallel named scopes are also allowed.
When a scope is finished callback execution, all the dependencies that were created in that scope will be destroyed, including singletons. This means that any instances of objects that were created within the scope will no longer be available.
Additionally, the container that was used to run the scope will also be destroyed, which means that any attempts to use the container after the scope has finished execution will result in an exception being thrown.
If you create a named scope using Container::scope()
method, you can access and modify the default bindings for that scope by calling Container::getBinder(string $scope)
method and using the returned Binder instance to make new bindings.
$container = new Container();
// Configure `root` scope bindings (the current instance)
$container->bindSingleton(Interface::class, Implementation::class);
// Configure `request` scope default bindings
// Prefer way to make many bindings
$binder = $container->getBinder('request');
$binder->bindSingleton(Interface::class, Implementation::class);
$binder->bind(Interface::class, factory(...));
New attributes
Here's an overview of the new attributes in Spiral's new container:
#[Singleton]
: This attribute is used to mark a class as a singleton. This attribute can be used in addition to the traditional bindSingleton()
method.
#[Scope(string $name)]
: This attribute is used to set a scope in which a dependency can be resolved.
#[Finalize(string $method)]
: This an experimental attribute is used to define a finalize method for a class. The finalize method will be called before the scope is being destroyed. This can be used to perform any necessary cleanup operations.
PHP 8.2 Fibers support
And also it's now fully compatible with PHP 8.2 Fibers. The static class \Spiral\Core\ContainerScope::getContainer()
will return correct container instance for the current fiber and scope.
2. Improved performance of Tokenizer component
Additionally, We improved application performance when utilizing tokenizer components. Specifically, the introduction of a TOKENIZER_CACHE_TARGETS
environment variable now allows for tokenizer listener caching. Upon the initial application bootstrapping, all found classes for each tokenizer listener will be cached. Subsequent bootstrapping processes will then retrieve the cached data, leading to faster and more efficient performance.
PR: #878
3. Console commands base on attributes
Developers can now create console commands using attributes.
PR: #872
Furthermore, if required arguments are not provided when calling a console command, the framework will prompt users to input the missing arguments rather than displaying an error. This is particularly useful for scaffolder commands like php app.php create:controller
, where developers previously needed to remember which arguments were required. With this new feature, all required arguments will be automatically prompted for, streamlining the development process.
When creating a console command, developers can use the Spiral\Console\Attribute\Question
attribute to specify the question text that should be displayed when the command is run and a required argument is missing.
final class CreateUser extends Command
{
#[Argument]
#[Question(question: 'Provide the User Email')]
private string $email;
}
4. Repeatable setters for filters
Another update is that the Setter attribute for the spiral/filters component is now repeatable. This means that it can be used multiple times on the same property, enabling more flexible and powerful filtering options.
class StoreUserFilter extends Filter
{
#[Post]
#[Setter(filter: 'strval')]
#[Setter('ltrim', '-')]
#[Setter('rtrim', ' ')]
#[Setter('htmlspecialchars')]
public string $name;
}
PR: #874
5. Cache key prefixes
Spiral also includes a new feature for cache storage aliases. Developers can now configure a key prefix for each alias, providing more granular control over caching options.
PR: #869
return [
'aliases' => [
'user-data' => [
'storage' => 'in-memory',
'prefix' => 'user_'
],
'blog-data' => [
'storage' => 'in-memory',
'prefix' => 'blog_'
],
],
'storages' => [
'in-memory' => [
'type' => 'roadrunner-local'
],
],
];
This allows developers to use the same cache storage for multiple purposes while still maintaining separate cache keys for each purpose. The prefix option can be used to differentiate cache keys and ensure that they do not overlap, improving the reliability and accuracy of caching in the application.
// Cache Manager $cache
$cache->storage('user-data')->set('data', 'foo');
$cache->storage('blog-data')->set('data', 'bar');
// Result
array:2 [
"user_data" => array:2 [
"value" => "foo"
"timestamp" => 1677694480
]
"blog_data" => array:2 [
"value" => "bar"
"timestamp" => 1677694480
]
]
6. Custom mail transport
Another new feature is the ability to register a custom mail transport factory via Bootloader. This can be useful in cases where developers want to use a non-standard mail transport with the SendIt component.
PR: #873
To register a new mail transport factory, developers can create a new Bootloader class
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\SendIt\TransportRegistryInterface;
use Symfony\Component\Mailer\Transport\c;
class AppBootloader extends Bootloader
{
public function boot(TransportRegistryInterface $registry): void
{
$registry->registerTransport(new SendmailTransportFactory(...));
}
}
Bug fixes
Spiral Framework 3.6 also includes several bug fixes to improve the reliability and stability of the framework.
- The first bug fix addresses a problem with the container factory where previously created objects were sometimes used instead of creating new objects with the provided arguments. This issue could cause unexpected behavior and was fixed.
$service = new Service();
$container->bindSingleton(Service::class, $service);
$service === $container->make(Service::class); // true
$service === $container->make(Service::class, ['foo' => 'bar']); // false
PR: #862
- The second bug fix resolves an error message that could occur in the Prototype component when updating a DOCComment. The error message
Updating PrototypeTrait DOCComment... DOCComment is missing
no longer appears.
PR: #865
- The third bug fix addresses a problem with validation nested filters. This issue could cause validation errors to be missed or incorrectly reported.
PR: #868
New Contributors
Full Changelog: 3.5.0...3.6.0
v3.5.0
What's Changed
- Improved the exception trace output for both the plain and console renderers to provide more detailed information about previous exceptions. by @butschster in #853
- Added named route patterns registry
Spiral\Router\Registry\RoutePatternRegistryInterface
to allow for easier management of route patterns. by @kastahov in #847 - Made the Verbosity enum injectable to allow for easier customization and management of verbosity levels from env variable
VERBOSITY_LEVEL
. by @kastahov in #846 - Deprecated Kernel constants and add new function
defineSystemBootloaders
to allow for more flexibility in defining system bootloaders. by @kastahov in #845
Full Changelog: 3.4.0...3.5.0
v3.4.0
What's Changed
- Medium Impact Changes
- [spiral/boot] Class
Spiral\Boot\BootloadManager\BootloadManager
is deprecated. Will be removed in version v4.0. #840 - [spiral/stempler] Adds null locale processor to remove brackets
[[ ... ]]
when don't use Translator component by @butschster in #843
- [spiral/boot] Class
- Other Features
- [spiral/session] Added session handle with cache driver by @kastahov in #835
- [spiral/router] Added routes with
PATCH
method intoroute:list
command by @gam6itko in #839 - [spiral/boot] Added
Spiral\Boot\BootloadManager\InitializerInterface
. This will allow changing the implementation
of this interface by the developer by @msmakouz #840 - [spiral/boot] Added
Spiral\Boot\BootloadManager\StrategyBasedBootloadManager
. It allows the implementation of a
custom bootloaders loading strategy by @msmakouz #840 - [spiral/boot] Added the ability to register application bootloaders via object instance or anonymous object.
- [spiral/boot] Removed
final
from theSpiral\Boot\BootloadManager\Initializer
class by @msmakouz #840
- Bug Fixes
- [spiral/views] Fixes problem with using view context with default value by @butschster in #842
- [spiral/queue] Added
Spiral\Telemetry\Bootloader\TelemetryBootloader
dependency to QueueBootloader by @butschster in #837 - [spiral/core] (PHP 8.2 support) Fixed problem with dynamic properties in
Spiral\Core\Container
by @msmakouz in #844
Full Changelog: 3.3.0...3.4.0
v3.3.0
What's Changed
- New [spiral/telemetry] component and spiral/otel-bridge by @butschster in #816
- Added
Spiral\Auth\Middleware\Firewall\RedirectFirewall
by @msmakouz in #827 - Created
TokenStorageProvider
to able manage token storages by @kastahov in #826 - Fixed error suppressing in the
Spiral\Http\Middleware\ErrorHandlerMiddleware
by @msmakouz in #833 - Router improvements by @msmakouz in #831
- Changed where Tokenizer listener events are called by @msmakouz in #825
New Contributors
- @kastahov made their first contribution in #826
- @alexander-schranz made their first contribution in #832
Full Changelog: 3.2.0...3.3.0
v3.2.0
What's Changed
- Adding context in the Queue by @msmakouz in #808
- Fixing RR mode checking by @msmakouz in #819
- CoreHandler do not mention internal errors in ClientException by @gam6itko in #813
- Adding Events interceptors by @msmakouz in #820
- Adding new options in the Queue by @msmakouz in #814
- Adding container to scope callback by @msmakouz in #821
- Improving ContainerException message by @msmakouz in #817
- Fixes problem with using push interceptors in Queue component by @butschster in #823
Full Changelog: 3.1.0...3.2.0