Harnessing the power of attributes in Sylius. A milestone for enhanced developer experience
Author:
Tymoteusz Stengert
Harnessing the power of attributes in Sylius. A milestone for enhanced developer experience
21-05-2024

BEWARE!!! This article is written by a developer for developers. You need to know the basics of PHP to understand it. ;)

In the evolving web development landscape, the Sylius framework has continually aimed to streamline processes and improve usability. Integrating attributes into Sylius has recently marked a significant advancement, providing developers with a more efficient and error-resistant way to manage entity and services’ metadata. 

From annotations to attributes. A development paradigm shift

Sylius has been allowing usage of Symfony / Doctrine attributes and annotations to manage entity and service metadata for years. However, Sylius stayed with YAML support for its configuration and used XML configuration for everything else, which has a particular drawback. It is necessary to maintain separate files for metadata definitions and code. This separation has often complicated the development process, requiring developers to frequently switch contexts between code and configuration files, potentially leading to inconsistencies and increased error rates. Moreover, the notations in the Sylius code base (XML) and final project (YAML) were usually different.

To reduce that, we can mix our service code with metadata. To do so, we can use annotations or attributes. Both of them reduce cognitive load related to syntax switches and, as a result, may provide a significant Developer Experience (DX) boost. Annotations, however, were helpful for many years and became obsolete. With the introduction of Attributes in PHP 8, the language offers a more streamlined and integrated way to handle metadata by allowing it to be declared in line with the code it describes. This means that attributes are part of the class, method, or property they refer to, thereby reducing the cognitive load on developers and minimizing the risk of errors. In addition, its direct integration with the PHP core provides much better support for different IDEs.

Attributes make metadata easier to find, read, and modify, improving overall code quality and maintainability. Furthermore, this approach aligns with contemporary coding practices that favour clarity and conciseness, ensuring Sylius remains at the forefront of PHP development trends.

In addition, using attributes makes programming safer. Developers can now explicitly discover the behavior of elements in their code through clear and descriptive syntax. This change improves the developer experience by making codebases easier to navigate and understand and accelerates the development process by reducing the time spent debugging and verifying external metadata files.

In summary, Sylius improves the efficiency and quality of development work by providing brand-new support for attributes. This transition supports a more robust, intuitive, and error-resistant approach to coding, reinforcing our commitment to improving the ease and effectiveness of e-commerce platform development.

Technical Insights: The Implementation of Attributes in Sylius

In Sylius, introducing attributes significantly improves Sylius behaviour customization. This enhancement streamlines the development process, primarily by embedding configuration details directly into the code where they are most relevant. 

Let's take a look at how custom promotion action is defined in Sylius at the moment:

// src/Promotion/Action/FreeItemPromotionActionCommand.php

<?php

declare(strict_types=1);

namespace App\Promotion\Action;

use Sylius\Component\Promotion\Action\PromotionActionCommandInterface;
use Sylius\Component\Promotion\Model\PromotionInterface;
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;

final class FreeItemPromotionActionCommand implements PromotionActionCommandInterface
{
    public function execute(PromotionSubjectInterface $subject, array $configuration, PromotionInterface $promotion): bool
    {
        // Promotion logic
    }

    public function revert(PromotionSubjectInterface $subject, array $configuration, PromotionInterface $promotion): void
    {
        // Promotion reverting logic
    }
}
# services.yaml

services:
    # other definitions

    App\Promotion\Action\FreeItemPromotionActionCommand:
        tags:
            - { 
                name: sylius.promotion_action, 
                type: free_item, 
                label: 'app.form.promotion_action.free_item', 
                form_type: App\Form\Type\Promotion\Action\FreeItemConfigurationType 
              }

You need to add this configuration even if autowiring and autoconfiguration are enabled because Sylius won’t know how to configure some tag values. Now, you must also be aware that reverse engineering is not easy. If you compare the same configuration for such service in YAML and XML

# services.xml

<service id="App\Promotion\Action\FreeItemPromotionActionCommand">
    <tag name="sylius.promotion_action" type="free_item" label="app.form.promotion_action.free_item" form-type="App\Form\Type\Promotion\Action\FreeItemConfigurationType" />
</service>
# services.yaml

App\Promotion\Action\FreeItemPromotionActionCommand:
    tags:
        - { 
            name: sylius.promotion_action, 
            type: free_item, 
            label: 'app.form.promotion_action.free_item', 
            form_type: App\Form\Type\Promotion\Action\FreeItemConfigurationType 
        }

You can notice that the XML key for the related form type is written as: “form-type”, while in YAML, it is: “form_type”. Another thing that can take you hours of debugging if you are not careful.

Now, the same with new Sylius attributes:

// src/Promotion/Action/FreeItemPromotionActionCommand.php

<?php

declare(strict_types=1);

namespace App\Promotion\Action;

use Sylius\Bundle\PromotionBundle\Attribute\AsPromotionAction;
use Sylius\Component\Promotion\Action\PromotionActionCommandInterface;
use Sylius\Component\Promotion\Model\PromotionInterface;
use Sylius\Component\Promotion\Model\PromotionSubjectInterface;
use App\Form\Type\Promotion\Action\FreeItemConfigurationType;

#[AsPromotionAction(type: 'free_item', label: 'app.form.promotion_action.free_item', formType: FreeItemConfigurationType::class)]
final class FreeItemPromotionActionCommand implements PromotionActionCommandInterface
{
    public function execute(PromotionSubjectInterface $subject, array $configuration, PromotionInterface $promotion): bool
    {
        // Promotion logic
    }

    public function revert(PromotionSubjectInterface $subject, array $configuration, PromotionInterface $promotion): void
    {
        // Promotion reverting logic
    }
}

Isn't that nice and simple?

The example above illustrates the major benefit of integrating attributes into Sylius. It offers a declarative approach that significantly cleans up the codebase. By using attributes, developers can directly annotate classes with related metadata. This eliminates the need for separate configuration files, thus enhancing discoverability and reducing the cognitive load on developers.

Attributes provide a more intuitive and efficient way to manage configuration and define behaviors within the framework. For instance, routing in Sylius (thanks to Symfony) can also be directly associated with the controller actions it affects, making the routes easier to trace and modify. 

Furthermore, the adoption of attributes in Sylius aligns with modern best practices in our ecosystem, which advocate for less boilerplate and more clarity. Decluttering the codebase may not only speed up the development cycle but also make it more accessible for new developers and reduce the likelihood of bugs.

In conclusion, implementing attributes in Sylius is a forward-thinking move that enriches the developer's toolkit with more concise and powerful coding options. Sylius sets a new standard for efficiency and clarity in PHP-based e-commerce platforms by simplifying the handling of configurations and integrating them more closely with the logic they define.

What can you configure with newly added attributes?

  • #[AsCommandDataTransformer]
  • #[AsDocumentationModifier]
  • #[AsPaymentConfigurationProvider]
  • #[AsOrderProcessor]
  • #[AsCartContext]
  • #[AsCatalogPromotionApplicatorCriteria]
  • #[AsCatalogPromotionPriceCalculator]
  • #[AsEntityObserver]
  • #[AsOrderItemUnitsTaxesApplicator]
  • #[AsProductVariantMapProvider]
  • #[AsTaxCalculationStrategy]
  • #[AsUriBasedSectionResolver]
  • #[AsLocaleContext]
  • #[AsCurrencyContext]
  • #[AsProductVariantResolver]
  • #[AsTaxCalculator]
  • #[AsShippingCalculator]
  • #[AsShippingMethodResolver]
  • #[AsShippingMethodRuleChecker]
  • #[AsPromotionAction]
  • #[AsPromotionCouponEligibilityChecker]
  • #[AsPromotionEligibilityChecker]
  • #[AsPromotionRuleChecker]
  • #[AsAttributeType]
  • #[AsChannelContext]
  • #[AsRequestBasedChannelResolver]
  • #[AsOrderItemsTaxesApplicator]
  • #[AsOrdersTotalsProvider]
  • #[AsPaymentMethodsResolver]
  • #[AsGatewayConfigurationType]

Benefits of using attributes in Sylius

The new syntax in Sylius offers several advantages:

Integrating metadata directly within the code reduces the potential for errors associated with external configuration files.

Attributes enhance the discoverability of configurations, as they are colocated with the code they configure.

For special cases such as custom order processors or promotion rules, attributes provide explicit control over service definitions without delving into documentation or our vendor code.

Community and future directions

Implementing attributes in Sylius was not an isolated decision but a community-driven enhancement. Key figures such as Jakub Tobiasz, Félix Fouillet, Jan Góralski, and Kamil Grygierzec played pivotal roles in advocating for and adopting this feature. Huge kudos to them!

While Sylius continues to support traditional methods like YAML and XML, introducing attributes provides developers with new tools to tailor their development approach. By doing this, we demonstrate our commitment to improving the developer experience and offer choices that best suit different programming tastes and needs.

Integrating attributes into Sylius in version 1.13  is more than a technical update—it is a great enhancement that gives you more freedom and potentially better efficiency, clarity, and control. I hope that as Sylius evolves, the continued focus on developer-centric features like attributes will solidify our position as the developer's framework of choice for e-commerce.

PS. It is not the last time you have read about attributes in Sylius. Stay tuned ;)

© Copyright 2024. Woven with 💛 in Łódź