Skip to content

Instantly share code, notes, and snippets.

@butschster
Last active August 15, 2023 12:41
Show Gist options
  • Save butschster/8f554769fa4f935c45f19a943025096b to your computer and use it in GitHub Desktop.
Save butschster/8f554769fa4f935c45f19a943025096b to your computer and use it in GitHub Desktop.
3.8.0.md

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 spiral/framework#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.

by @msmakouz in spiral/framework#925

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());
    }
}

by @msmakouz in spiral/framework#961

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);
    }
}

by @msmakouz in spiral/framework#931

5. Container improvements

In this update, our primary focus has been on elevating the overall performance of the container

  1. We have successfully migrated a significant portion of the internal operations from runtime to configuration time.
  2. 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 of Spiral\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 spiral/framework#941 spiral/framework#935 spiral/framework#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 address this, we've introduced an intelligent exception mechanism in the container management process.

If you attempt to redefine a singleton that already exists in the container, the framework will now throw an exception Spiral\Core\Exception\Binder\SingletonOverloadException to notify you about the ongoing singleton definition.

To redefine a singleton, initiate the process by explicitly removing the existing binding linked to the singleton.

$container->removeBining(...) 

Once the current binding is removed, proceed with the definition of the new configuration for the singleton.

by @msmakouz in spiral/framework#955

6. Tokenizer improvements

Enum & Interface loaders

I want to highlight something cool in spiral/tokenizer component. Well, it's got this new thing with class listeners that seriously boosts how we analyze code. Especially handy when we're tackling big codebases.

Here's the deal: before, we could only check out PHP classes. But now, we're getting more firepower. We can also dive into Enums and class interfaces. How cool is that?

To make it work, just tweak some settings in app/config/tokenizer.php like so:

return [
    // ...
    'load' => [
        'classes' => true,
        'enums' => true,
        'interfaces' => true,
    ],
];

With these tweaks done, you're ready to rock with Enums and interfaces. Check this out:

use Spiral\Tokenizer\TokenizationListenerInterface;

class EnumsListener implements TokenizationListenerInterface
{
    public function listen(\ReflectionClass $class): void
    {
        if (!$class instanceof \ReflectionEnum) {
            return;
        }

        // ...
    }

    public function finalize(): void
    {
        
    }
}

by @andrey-mokhov in spiral/framework#947

Added tokenizer:info console command

We also added a simple command to display tokenizer configuration.

Just run

php app.php tokenizer:info

Once you've done that, you will see an information about tokenizer configutration.

Included directories:
+-----------------------------+-------+
| Directory                   | Scope |
+-----------------------------+-------+
| app/                        |       |
| vendor/spiral/validator/src |       |
+-----------------------------+-------+

Excluded directories:
+----------------+-------+
| Directory      | Scope |
+----------------+-------+
| app/resources/ |       |
| app/config/    |       |
| tests          |       |
| migrations     |       |
+----------------+-------+
Tokenizer cache: disabled

To enable cache, add "TOKENIZER_CACHE_TARGETS=true" to your .env file.
Read more at https://spiral.dev/docs/advanced-tokenizer/#class-listeners

by @butschster in spiral/framework#963

7. Added PHP 8.1 support for prototype injector

Spiral comes with spiral/prototype component that speeds up the development of application services, controllers, middleware, and other classes via AST modification (a.k.a. it writes code for you). The extension includes IDE friendly tooltips for most common framework components and Cycle Repositories.

We've now made sure that the framework supports PHP 8.1. This means that when you run the command php app.php prototype:inject, all the dependencies used inside a class will be automatically injected into a constructor.

Features

  1. Dependency injection via constructor readonly property promotion (PHP 8.1 feature).
  2. Properties are always injected with types.
  3. Removed PHPDoc. It duplicates types and carries no payload.

Here is an example

namespace App\Endpoint\Web;

use Spiral\Prototype\Traits\PrototypeTrait;
use Spiral\Router\Annotation\Route;
use Spiral\Views\ViewsInterface;

final class HomeController
{
    use PrototypeTrait;

    public function __construct(
        private readonly ViewsInterface $views
    ) {
    }

    #[Route(route: '/', name: 'index')]
    public function index(): string
    {
        return $this->views->render('home', [
            'users' => $this->users->findAll() <====== Should be injected after command execution
        ]);
    }
}

Run the command

php app.php prototype:inject

Once you've done that, your code will automatically transform to something like this:

namespace App\Endpoint\Web;

use Spiral\Prototype\Traits\PrototypeTrait;
use Spiral\Router\Annotation\Route;
use Spiral\Views\ViewsInterface;
use App\Infrastructure\Persistence\CycleORMUserRepository;

final class HomeController
{
    use PrototypeTrait;

    public function __construct(
        private readonly CycleORMUserRepository $users, 
        private readonly ViewsInterface $views <========== Injected dependency
    ) {
    }

    // ...
}

by @msmakouz in spiral/framework#917

  • [spiral/attributes] Adds the ability to disable annotations reader support and the ability to replace instantiator for attributes reader. by @butschster in spiral/framework#940
  • Added support psr/http-message v2 by @roxblnfk in spiral/framework#944
  • Added PHPUnit 10 support by @msmakouz in spiral/framework#927

Bug Fixes

New Contributors

Full Changelog: https://github.com/spiral/framework/compare/3.7.1...3.8.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment