<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Locastic</title>
    <description>The latest articles on DEV Community by Locastic (@locastic).</description>
    <link>https://dev.to/locastic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F432457%2F13cc6a25-268c-4a97-b625-c1ea50b0ba87.jpg</url>
      <title>DEV Community: Locastic</title>
      <link>https://dev.to/locastic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/locastic"/>
    <language>en</language>
    <item>
      <title>Can you use Sylius to build booking systems?</title>
      <dc:creator>Locastic</dc:creator>
      <pubDate>Wed, 25 Aug 2021 12:00:32 +0000</pubDate>
      <link>https://dev.to/locastic/can-you-use-sylius-to-build-booking-systems-18gn</link>
      <guid>https://dev.to/locastic/can-you-use-sylius-to-build-booking-systems-18gn</guid>
      <description>&lt;p&gt;For the last few months, we’ve been working on the MVP for a project similar to the Airbnb booking engine. Even though we were building an MVP, we still wanted to use a top-notch stack, amazingly fast application, and we wanted to reach the market as soon as possible. On the frontend, we are using Next.js with Vercel as hosting, and on the backend side, we are using Sylius as a headless e-commerce solution with our custom dedicated servers.&lt;/p&gt;

&lt;p&gt;Was this a good idea, is it even possible? Let’s find out.&lt;/p&gt;

&lt;p&gt;Is Sylius ready to be a booking engine?&lt;br&gt;
At the moment, it is probably not ready to be a booking engine, at least not out of the box. But booking engines can be quite similar to e-commerce, and the flexibility of Sylius offers you a way to use it for many different purposes.&lt;/p&gt;

&lt;p&gt;Sylius is using ApiPlatform (still a work in progress and in the experimental phase) to provide a headless system. Since our team in Locastic are experts in the entire stack (Symfony, Sylius, ApiPlatform) and since we calculated that we will save some time in getting to the market faster with Sylius – we wanted to give it a try.&lt;/p&gt;

&lt;p&gt;Please note that I am not saying that this is the best way for building the booking engine, if you have a huge and complex project, maybe a better solution is to build an entire engine from the scratch or use existing solutions. Also, in this situation, you need to know Sylius and ApiPlatform in the depth, almost every detail and you will have a lot of overhead (features from Sylius that you are not using).&lt;/p&gt;

&lt;p&gt;Modeling accommodations a.k.a. SyliusProduct&lt;br&gt;
For this MVP project, the most complex part was modelling Products. Our products are actually Accommodations. Even basic accommodation has:&lt;/p&gt;

&lt;p&gt;different attributes (for example pets allowed, has a pool, smoking allowed, free parking on premises, bbq and etc). In general, any amenities are properties in our case and we will use this for searching and filtering accommodations&lt;br&gt;
a different price and availability per date&lt;br&gt;
number of minimal days for booking (min stay)&lt;br&gt;
number of guests, with different settings for children and infants&lt;br&gt;
shipping is not required&lt;br&gt;
Using Sylius Product Attributes&lt;br&gt;
When we are talking about product attributes, we are usually dealing with them in two ways. Adding them in the code (extending Product or ProductVariant entities, forms and etc.), or using built-in Sylius properties. In general, the first way requires a lot of coding but you will maybe gain some performance in searching and managing queries later, so if you have a few properties that are always required, this could be a way to go.&lt;/p&gt;

&lt;p&gt;The second way is more flexible and since we already have 100+ Attributes, it was a logical choice for us. Also, we don’t want to spend a few hours/days each time to introduce a new Attribute when this can be added by the Client through the Sylius administration. Each new Attribute that is added is exposed via API to the frontend and it is shown in the filters list as part of the search form.&lt;/p&gt;

&lt;p&gt;Since we have a lot of Attributes we don’t want to bother users to search and add them one by one to the Product (current Sylius UX in administration), so we changed the ProductFactory service to add all of the Attributes to each Product/Accommodation. Here is the source code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

declare(strict_types=1);

namespace App\Factory;

use App\Entity\Product\Product;
use App\Entity\Product\ProductAttributeValue;
use App\Entity\Product\ProductOption;
use App\Repository\Product\ProductOptionRepository;
use Sylius\Component\Product\Factory\ProductFactoryInterface;
use Sylius\Component\Product\Model\ProductInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Webmozart\Assert\Assert;

final class ProductFactory implements ProductFactoryInterface
{
    private ProductFactoryInterface $decoratedFactory;

    private ProductOptionRepository $productOptionRepository;

    private RepositoryInterface $productAttributeRepository;

    private FactoryInterface $productAttributeValueFactory;

    public function __construct(
        ProductFactoryInterface $factory,
        ProductOptionRepository $productOptionRepository,
        RepositoryInterface $productAttributeRepository,
        FactoryInterface $productAttributeValueFactory
    ) {
        $this-&amp;gt;decoratedFactory = $factory;
        $this-&amp;gt;productOptionRepository = $productOptionRepository;
        $this-&amp;gt;productAttributeRepository = $productAttributeRepository;
        $this-&amp;gt;productAttributeValueFactory = $productAttributeValueFactory;
    }

    public function createNew(): ProductInterface
    {
        /** @var Product $product */
        $product = $this-&amp;gt;decoratedFactory-&amp;gt;createNew();

        $optionDate = $this-&amp;gt;productOptionRepository-&amp;gt;findOneByCode('date');
        Assert::isInstanceOf($optionDate, ProductOption::class);

        $product-&amp;gt;addOption($optionDate);

        $attributes = $this-&amp;gt;productAttributeRepository-&amp;gt;findAll();

        foreach ($attributes as $attribute) {
            /** @var ProductAttributeValue $attributeValue */
            $attributeValue = $this-&amp;gt;productAttributeValueFactory-&amp;gt;createNew();
            $attributeValue-&amp;gt;setAttribute($attribute);
            $product-&amp;gt;addAttribute($attributeValue);
        }

        return $product;
    }

    public function createWithVariant(): ProductInterface
    {
        return $this-&amp;gt;decoratedFactory-&amp;gt;createWithVariant();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since most of these Attributes are true/false, all the user needs to do is to mark the true ones.&lt;/p&gt;

&lt;p&gt;Sylius ProductVariants to the rescue&lt;br&gt;
Let’s think a little bit about ProductVariants. In their core definition, they are the same Product but with some different variations, as the name says. In Sylius we can generate ProductVarints from the ProductOption, and out of the box we have the possibility to add different price, different ProductOptionValue, different stock (availability). So it is a perfect match for our Accommodation (Product) which will have different price, availability for each date (ProductVariant).&lt;/p&gt;

&lt;p&gt;If you paid attention above to the ProductFactory service, you noticed this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$this-&amp;gt;productOptionRepository-&amp;gt;findOneByCode('date');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we created a ProductOption with the code/name ‘date’. Then we assign all the possible values for this Option for example all the dates from today to the +2years (note: don’t do this manually, also you will need to create CronJob that will remove the date in the past and add new dates for example every day). From this option, we can now generate all the required variants.&lt;/p&gt;

&lt;p&gt;Shipping is not required, tracking is required&lt;br&gt;
Since we are not shipping any of our products, we have a requirement to mark isShippingRequired as false. We want to be sure that this requirement is always fulfilled, and we don’t want to bother our client with this, so we simply override the method in the ProductVariant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   // none of items requires shipping, always false
    public function isShippingRequired(): bool
    {
        return false;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A similar thing we did with isTracked method, just we are returning true there. In general, Sylius Admin is not very user friendly for this type of project, since we are managing literally thousands of ProductVariants, so in our case, we removed the entire part of Sylius admin where ProductVariatns are managed, and put all of this in the simple calendar, where user can see price, availability, reservations, can close the dates and etc. Actually, we build a more native and domain friendly UX for this type of project.&lt;/p&gt;

&lt;p&gt;With isShippingRequired === false, we are also removing a step with delivery from our checkout process, since we are not shipping anything.&lt;/p&gt;

&lt;p&gt;Sylius cart/order modifications for a booking system&lt;br&gt;
I already mentioned how we removed the shipping step from the checkout, in general, if you need any other adjustment you will need to deal with Sylius checkout workflow, also if you are using the APIs the things are slightly different.&lt;/p&gt;

&lt;p&gt;What we also did here is allowing only one product (there is almost a 0% chance that someone will book two accommodations at the same time. Since the booking domain is slightly different, we wanted to adjust adding to the cart to the more natural flow. So instead of sending an array of ProductVariant ids, we are sending Product Id and reservation starting and add date. Bellow is part of the code of our AddItemsToCartProcessor that is used inside CommandHander.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

declare(strict_types=1);

namespace App\Order\Processor;

use App\Entity\Order\Order;
use App\Entity\Order\OrderItem;
use App\Entity\Product\Product;
use App\Entity\Product\ProductVariant;
use App\Message\Order\BaseAddItemsToOrderMessage;
use App\Repository\Product\ProductVariantRepository;
use Sylius\Component\Order\Modifier\OrderItemQuantityModifierInterface;
use Sylius\Component\Order\Modifier\OrderModifierInterface;
use Sylius\Component\Order\Processor\OrderProcessorInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;

class AddItemsToCartProcessor
{
    // ...

    public function process(Order $cart, Product $product, BaseAddItemsToOrderMessage $message): Order
    {
        $cart-&amp;gt;clearItems();

        $productVariants = $this-&amp;gt;productVariantRepository-&amp;gt;findAvailableByProductCodeBetweenDates(
            $message-&amp;gt;getProductCode(),
            $message-&amp;gt;getDateFrom(),
            $message-&amp;gt;getDateTo()
        );

        if (empty($productVariants)) {
            return $cart;
        }

        foreach ($productVariants as $productVariant) {
            /** @var OrderItem $cartItem */
            $cartItem = $this-&amp;gt;orderItemFactory-&amp;gt;createNew();

            /** @var ProductVariant $productVariant */
            $cartItem-&amp;gt;setVariant($productVariant);
            $cartItem-&amp;gt;setDate($productVariant-&amp;gt;getDate());
            $cartItem-&amp;gt;setVariantName($productVariant-&amp;gt;getName());
            $cartItem-&amp;gt;setProductName($productVariant-&amp;gt;getProduct()-&amp;gt;getName());

            $this-&amp;gt;orderItemQuantityModifier-&amp;gt;modify($cartItem, 1);
            $this-&amp;gt;orderModifier-&amp;gt;addToOrder($cart, $cartItem);
        }

        // ... some custom logic ... 

        $this-&amp;gt;orderProcessor-&amp;gt;process($cart);

        return $cart;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t forget to create a custom API endpoint, with a custom message and message handler.&lt;/p&gt;

&lt;p&gt;Booking (SyliusOrder) validation&lt;br&gt;
You can notice above that we are not validating our request anywhere, and we should do that. We should validate first basic things, like data received from the application. For basic validation of data, we are using SymfonyValidor, with build validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App\Message\Order\AddItemsToOrderMessage:
    constraints:
        - App\Validator\ValidReservationRequest: ~ 
    properties:
        productCode:
            - NotNull: ~
            - NotBlank: ~
        dateFrom:
            - Type: \DateTimeInterface
            - GreaterThanOrEqual: today
        dateTo:
            - Type: \DateTimeInterface
            - GreaterThan:
                  propertyPath: dateFrom
        adults:
            - Type: integer
            - PositiveOrZero: ~
        children:
            - Type: integer
            - PositiveOrZero: ~
        infants:
            - Type: integer
            - PositiveOrZero: ~
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Probably you already noticed something not standard-out-of-the box validation here, and yes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   constraints:
        - App\Validator\ValidReservationRequest: ~ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is our most important and custom validation. Here we are validating things as product exist, minimal stay, number of guests, number of infants, availability and etc. Of course, this validation can be even more complex, this is depending on your requirements, but this is just an example of what you should have in mind.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;code&amp;gt;&amp;lt;?php

declare(strict_types=1);

namespace App\Validator;

use App\Entity\Product\Product;
use App\Message\Order\BaseAddItemsToOrderMessage;
use App\Repository\Product\ProductRepository;
use App\Repository\Product\ProductVariantRepository;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;

class ValidReservationRequestValidator extends ConstraintValidator
{
    private ProductRepository $productRepository;

    private ProductVariantRepository $productVariantRepository;

    public function __construct(
        ProductRepository $productRepository,
        ProductVariantRepository $productVariantRepository
    ) {
        $this-&amp;gt;productRepository = $productRepository;
        $this-&amp;gt;productVariantRepository = $productVariantRepository;
    }

    public function validate($addItemsToOrderMessage, Constraint $constraint): void
    {
        if (!$constraint instanceof ValidReservationRequest) {
            throw new UnexpectedTypeException($constraint, ValidReservationRequest::class);
        }

        /** @var BaseAddItemsToOrderMessage $addItemsToOrderMessage */
        Assert::isInstanceOf($addItemsToOrderMessage, BaseAddItemsToOrderMessage::class);

        $product = $this-&amp;gt;productRepository-&amp;gt;findOneByCode($addItemsToOrderMessage-&amp;gt;getProductCode());

        // check if product exists
        if (!$product instanceof Product) {
            $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageProductNotFound)
                -&amp;gt;addViolation();

            return;
        }

        // check minimal stay requirements
        if ($product-&amp;gt;getMinStay() &amp;gt; $addItemsToOrderMessage-&amp;gt;getStayLength()) {
            $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageMinimalStay)
                -&amp;gt;setParameter('{{ minStay }}', (string) $product-&amp;gt;getMinStay())
                -&amp;gt;addViolation();

            return;
        }

        // check number of guests (noOfGuests = adults + children)
        if ($product-&amp;gt;getNumberOfGuests() &amp;lt; $addItemsToOrderMessage-&amp;gt;getNumberOfGuests()) {
            $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageNumberOfGuests)
                -&amp;gt;setParameter('{{ numberOfGuests }}', (string) $product-&amp;gt;getNumberOfGuests())
                -&amp;gt;addViolation();

            return;
        }

        // check number if infants
        if ($product-&amp;gt;getNumberOfInfants() &amp;lt; $addItemsToOrderMessage-&amp;gt;getInfants()) {
            // infants are not allowed
            if ($product-&amp;gt;getNumberOfInfants() === 0) {
                $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageInfantsNotAllowed)
                    -&amp;gt;addViolation();

                return;
            }

            $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageNumberOfInfants)
                -&amp;gt;setParameter('{{ numberOfInfants }}', (string) $product-&amp;gt;getNumberOfGuests())
                -&amp;gt;addViolation();

            return;
        }

        // ensure dates are sent without defined time
        if ($addItemsToOrderMessage-&amp;gt;getDateFrom() != $addItemsToOrderMessage-&amp;gt;getDateFrom()-&amp;gt;setTime(0, 0)) {
            $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageTimeDefined)
                -&amp;gt;addViolation();

            return;
        }

        if ($addItemsToOrderMessage-&amp;gt;getDateTo() != $addItemsToOrderMessage-&amp;gt;getDateTo()-&amp;gt;setTime(0, 0)) {
            $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageTimeDefined)
                -&amp;gt;addViolation();

            return;
        }

        if ($addItemsToOrderMessage-&amp;gt;getStayLength() !== $this-&amp;gt;getNumberOfAvailableProductVariants($addItemsToOrderMessage)) {
            $this-&amp;gt;context-&amp;gt;buildViolation($constraint-&amp;gt;messageDatesAreNotAvailable)
                -&amp;gt;addViolation();

            return;
        }
    }

    private function getNumberOfAvailableProductVariants(BaseAddItemsToOrderMessage $addItemsToOrderMessage): int
    {
        return count(
            $this-&amp;gt;productVariantRepository-&amp;gt;findAvailableByProductCodeBetweenDates(
                $addItemsToOrderMessage-&amp;gt;getProductCode(),
                $addItemsToOrderMessage-&amp;gt;getDateFrom(),
                $addItemsToOrderMessage-&amp;gt;getDateTo()
            )
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, can Sylius be a booking engine?&lt;br&gt;
Although it would take ages to describe the custom adaptations we did inside this project, it is possible!&lt;/p&gt;

&lt;p&gt;Product and ProductVariants can work perfectly to build availability and pricing calendar for accommodations, we can easily remove shipping and add tracking to our ProductVariants, also it is very easy to add custom validation. Yes, you are getting a lot of overhead from Sylius also, the Sylius Admin UX is not the most friendly one for this type of project and etc. It is up to the team to decide if this approach is good for the project or not – but for us, it’s been working like a charm.&lt;/p&gt;

&lt;p&gt;Let us know in the comments what do you think about this approach, or drop us an email if you need help with any type of Sylius/Symfony projects.&lt;/p&gt;

</description>
      <category>sylius</category>
      <category>software</category>
      <category>symfony</category>
      <category>booking</category>
    </item>
    <item>
      <title>Quick 7 tips for Symfony starters</title>
      <dc:creator>Locastic</dc:creator>
      <pubDate>Fri, 05 Feb 2021 12:32:24 +0000</pubDate>
      <link>https://dev.to/locastic/quick-7-tips-for-symfony-starters-jip</link>
      <guid>https://dev.to/locastic/quick-7-tips-for-symfony-starters-jip</guid>
      <description>&lt;p&gt;From a version to another reaching 5.2.1 as the latest stable release, Symfony is scaling to become in line with PHP Framework on the throne.&lt;/p&gt;

&lt;p&gt;For all of you that have been living under the rock, Symfony is an Open Source PHP Framework for web applications that thousands of web sites and applications rely on. It’s a set of standalone PHP components that most of the leading PHP projects such as Drupal and Laravel use to build their own applications.&lt;/p&gt;

&lt;p&gt;For the past few years, Symfony has been the main backend framework for the majority of Locastic projects so I extracted these quick tips from a perspective of a backender with major experience of working on many projects built on top of this growing framework.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Make use of others experience
&lt;/h1&gt;

&lt;p&gt;Nowadays, regarding the COVID-19 pandemic, the migration for e-Commerce platforms seems to be an obligation for too many commercial companies and even for small merchants. Once engaged to develop one of these platforms you definitely should use Sylius since it covers almost all e-commerce features and furthermore it’s an Open-Source headless e-commerce platform built on top of Symfony.&lt;/p&gt;

&lt;p&gt;For example, take a look at the TommySpiza project that I contributed to and which uses the full potential of the Sylius platform.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. You don’t need a shotgun to kill an ant
&lt;/h1&gt;

&lt;p&gt;Many times, Symfony developers use FOSUserBundle in an API context to only store users in a database without using the full potential of this bundle.&lt;/p&gt;

&lt;p&gt;This is a bad habit of integrating third-party libraries that come with plenty of features and then use only one between them.&lt;/p&gt;

&lt;p&gt;So if you are not willing to use functionalities such as login, registration, reset the password, profile and groups just refer to this guide in order to create your custom authentication system with the guard.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Never be afraid to try something new
&lt;/h1&gt;

&lt;p&gt;Often, the first thing that came to a Symfony beginner’s mind is that Symfony is an MVC framework! The good news, this is INCORRECT.&lt;/p&gt;

&lt;p&gt;Symfony is never defined as an MVC framework but an HTTP framework&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fabien Potencier&lt;/strong&gt;&lt;br&gt;
From this basis, try to discover the ADR pattern specified by Paul M. Jones, it will amaze you especially with Symfony’s new features such as Autowiring which is an automatic constructor dependency injection system that reduces the configuration when managing services within the container.&lt;br&gt;
Here’s an example of a Mailer service that needs a Logger to operate :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classic fashion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# app/config/services.yml&lt;br&gt;
services:&lt;br&gt;
    logger:&lt;br&gt;
        class: App\Service\Logger&lt;br&gt;
    mailer:&lt;br&gt;
        class: App\Service\Mailer&lt;br&gt;
        arguments: ['@logger']&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Magic fashion&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;# app/config/services.yml&lt;br&gt;
services:&lt;br&gt;
    mailer:&lt;br&gt;
        class: App\Service\Mailer&lt;br&gt;
        autowire: true&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Don’t reinvent the wheel
&lt;/h1&gt;

&lt;p&gt;Surely you’ve heard that while programming it’s discouraged to reinvent the wheel. This really depends on what your aim is. From the perspective of a learning mindset, this seems to be incorrect especially when re-inventing you will learn how solutions are made when resolving problems and you will get a deeper understanding of how they work, but according to a pragmatic mindset, you definitely want to resolve problems at minimal cost and give more importance to the re-use rather than the invention especially when it comes to well-documented existing solutions.&lt;/p&gt;

&lt;p&gt;So for example, In order to build a REST API on top of Symfony, you don’t need to spend too much time on purpose. Do it in a faster, cleaner, and more effective way using API Platform Framework, which comes with an easy-to-use and powerful library to create hypermedia-driven REST APIs.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Good Developers Write Good Code
&lt;/h1&gt;

&lt;p&gt;The main mission of a software developer is to ship software that works but also which is easily maintainable. To do so, you will usually work with other contributors that may use, maintain or even extend your code so this latter needs to be as clean and understandable as possible to ensure productivity and efficiency. Good code is subjective, so try on building the best code possible. In my opinion, to be a good Symfony developer you must first be able to write good code. For such a purpose try to learn Coding Standards.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Think twice, code once, but always test!
&lt;/h1&gt;

&lt;p&gt;There is a knock on the Symfony framework that it is slow when building applications that consume a huge amount of data. So, if you once need to store for example 10.000 records at the same time, you need to figure out how to do it without affecting performance. Batch processing is the best solution for such a use case, just have a look at the bunch of code below.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$batchSize = 20;&lt;br&gt;
for ($i = 1; $i &amp;lt;= 10000; ++$i) { &lt;br&gt;
   $symfonista = new Symfonista; &lt;br&gt;
   $symfonista-&amp;gt;setStatus('newbie');&lt;br&gt;
   $symfonista-&amp;gt;setUsername('symfonista' . $i);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$em-&amp;gt;persist($symfonista);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if (($i % $batchSize) === 0) {&lt;br&gt;
      $em-&amp;gt;flush();&lt;br&gt;
      $em-&amp;gt;clear(); // Detaches all objects from Doctrine!&lt;br&gt;
   }&lt;br&gt;
}&lt;br&gt;
$em-&amp;gt;flush(); //Persist objects that did not make up an entire batch&lt;br&gt;
$em-&amp;gt;clear();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This seems to be cool at the first sight, but be careful every new line of code you write is potentially adding new bugs, so don’t forget to write your functional and unit tests.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;// inside TestCase class &lt;br&gt;
$entityManagerMock = $this&lt;br&gt;
   -&amp;gt;getMockBuilder('Doctrine\ORM\EntityManager')&lt;br&gt;
   -&amp;gt;disableOriginalConstructor()&lt;br&gt;
   -&amp;gt;getMock();&lt;br&gt;
$entityManagerMock&lt;br&gt;
   -&amp;gt;expects($this-&amp;gt;any())&lt;br&gt;
   -&amp;gt;method('persist')&lt;br&gt;
   -&amp;gt;will($this-&amp;gt;returnValue(true));&lt;br&gt;
$entityManagerMock&lt;br&gt;
   -&amp;gt;expects($this-&amp;gt;any())&lt;br&gt;
   -&amp;gt;method('flush')&lt;br&gt;
   -&amp;gt;will($this-&amp;gt;returnValue(true));&lt;br&gt;
$entityManagerMock&lt;br&gt;
   -&amp;gt;expects($this-&amp;gt;any())&lt;br&gt;
   -&amp;gt;method('clear')&lt;br&gt;
   -&amp;gt;will($this-&amp;gt;returnValue(true));&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  7. Ask an experimenter, don’t ask the doctor
&lt;/h1&gt;

&lt;p&gt;Let’s say you are building a feature estimate. As a junior, no one expects that you know exact numbers, but you can always use the cautious approach – whatever your estimate is, double it! Be careful because there will always be some secret failures inside hidden functionalities you develop and that you should take care of eventually.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who knows, maybe I’m the experimenter you’d be looking for?&lt;/em&gt;&lt;br&gt;
Get in touch if you’re ready to start your Symfony journey or share this article on social media and I’ll jump in for a further discussion.&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>backend</category>
      <category>software</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Administrate your administrators with Sylius RBAC </title>
      <dc:creator>Locastic</dc:creator>
      <pubDate>Mon, 18 Jan 2021 07:08:28 +0000</pubDate>
      <link>https://dev.to/locastic/administrate-your-administrators-with-sylius-rbac-g8f</link>
      <guid>https://dev.to/locastic/administrate-your-administrators-with-sylius-rbac-g8f</guid>
      <description>&lt;p&gt;Not long ago, we went through a common, yet complicated demand on a Sylius shop : “ Contextualize access rights according to the admin user logged in“&lt;/p&gt;

&lt;p&gt;At the moment, there are 2 solutions to do so in the Sylius Ecosystem.&lt;/p&gt;

&lt;p&gt;The one that comes with SyliusPlus and that I yet have to try.&lt;br&gt;
And the one from BitBag Plugin, that I also have not tried yet.&lt;br&gt;
But since that for the purpose of the project, we needed a very basic feature, and we did not want to bother understanding and overriding any of those two existing (and paid) solutions, we decided to use a custom one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Concept
&lt;/h1&gt;

&lt;p&gt;We chose a role-based approach, with a grant/deny strategy. As a matter of fact, it is quite similar to the one that Symfony offers out of the box with the security component. So the first step was to define the data structure. We opted in for YAML defined roles, because at first, the roles should only be manageable via the code, but can then be deported to the admin panel. And since YAML is easily convertible to PHP array, it sounded like the top choice for this task.&lt;/p&gt;

&lt;h1&gt;
  
  
  Data structure
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;code&amp;gt;parameters:&lt;br&gt;
    roles_accesses:                   &lt;br&gt;
        {roleName}:&lt;br&gt;
            strategy: 'denyAllExcept'&lt;br&gt;
            redirect_if_not_granted: 'sylius_admin_dashboard'&lt;br&gt;
            allow_partials: true&lt;br&gt;
            exceptions:&lt;br&gt;
                routes:&lt;br&gt;
                    - 'sylius_admin_dashboard'&lt;br&gt;
                    - 'sylius_admin_taxon_*'&lt;br&gt;
                resources:&lt;br&gt;
                    - 'sylius.product.index'&lt;br&gt;
            abstain_for:&lt;br&gt;
                routes:&lt;br&gt;
                    - 'sylius_admin_admin_user_update'&lt;br&gt;
                resources:&lt;br&gt;
                    - 'sylius.admin_user.update'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is the structure we are using. It is a simple Symfony parameter, since we went with a denyAllExcept strategy, all the routes or resources in exceptions are allowed. We also decided to allow all partial routes at once since a lot of admin pages rely on those (product autocomplete ie.). Regarding the abstain_for part, we will get back to that later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Get the file and deny access
&lt;/h1&gt;

&lt;p&gt;Let’s now use this new parameter in our app. And to do so, we will use a leftover from old Sylius versions that were never removed, fortunately.&lt;/p&gt;

&lt;p&gt;In each and every ResourceController there is a check in the form of $this-&amp;gt;isGrantedOr403(). And if we dig a little bit more, it is calling a DisabledAuthorizationChecker that always return true. Obviously, this is not useful at all like that, but the main point is that Sylius is giving us a way to develop that, without much effort.&lt;/p&gt;

&lt;p&gt;We have to create a new class that implements this AuthorizationCheckerInterface from SyliusResourceBundle.&lt;/p&gt;

&lt;p&gt;But things would be too easy if that was it, wouldn’t they?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;?php&lt;br&gt;
declare(strict_types=1);&lt;br&gt;
namespace App\RightsManagement\Controller;&lt;br&gt;
use App\RightsManagement\Exception\AdminUserAccessDeniedException;&lt;br&gt;
use App\RightsManagement\Provider\ResourceProviderInterface;&lt;br&gt;
use App\RightsManagement\Security\AdminAuthorizationCheckerInterface;&lt;br&gt;
use Sylius\Bundle\ResourceBundle\Controller\AuthorizationCheckerInterface;&lt;br&gt;
use Sylius\Bundle\ResourceBundle\Controller\RequestConfiguration;&lt;br&gt;
final class AuthorizationChecker implements AuthorizationCheckerInterface&lt;br&gt;
{&lt;br&gt;
    private AdminAuthorizationCheckerInterface $adminAuthChecker;&lt;br&gt;
    private ResourceProviderInterface $resourceProvider;&lt;br&gt;
    public function __construct(&lt;br&gt;
        AdminAuthorizationCheckerInterface $adminAuthChecker,&lt;br&gt;
        ResourceProviderInterface $resourceProvider&lt;br&gt;
    ) {&lt;br&gt;
        $this-&amp;gt;adminAuthChecker = $adminAuthChecker;&lt;br&gt;
        $this-&amp;gt;resourceProvider = $resourceProvider;&lt;br&gt;
    }&lt;br&gt;
    public function isGranted(RequestConfiguration $configuration, string $permission): bool&lt;br&gt;
    {&lt;br&gt;
        $request = $configuration-&amp;gt;getRequest();&lt;br&gt;
        $routeName = $request-&amp;gt;attributes-&amp;gt;get('_route');&lt;br&gt;
        $resource = $this-&amp;gt;resourceProvider-&amp;gt;getFromPermissionAndRequestConfiguration($permission, $configuration);&lt;br&gt;
        if (!$this-&amp;gt;adminAuthChecker-&amp;gt;isGranted($routeName, $permission, $resource)) {&lt;br&gt;
            throw new AdminUserAccessDeniedException('You shall not pass');&lt;br&gt;
        }&lt;br&gt;
        return true;&lt;br&gt;
    }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So here, we are performing a few basic, but needed tasks for our feature.&lt;/p&gt;

&lt;p&gt;First of all, we fetch the route name. It will be useful later to perform checks on it.&lt;/p&gt;

&lt;p&gt;Secondly, we fetch the resource concerned by the action. Without going into details, it will fetch the resource in case of update, delete or show and null for other cases. The third and final action is to perform a check against a Voter with permission, the resource, and the route.&lt;/p&gt;

&lt;h1&gt;
  
  
  Voting is a duty
&lt;/h1&gt;

&lt;p&gt;Regarding the voter implementation its role will be to first filter the strictly granted or denied routes. You know, those in exceptions . Will they be routes, or resources.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public function isGranted(?string $routeName, ?string $permission, ?ResourceInterface $resource = null): bool&lt;br&gt;
    {&lt;br&gt;
        if (null !== $routeName) {&lt;br&gt;
            $routeVoter = new RouteVoter($this-&amp;gt;adminUserRightsProvider);&lt;br&gt;
            $routeDecision = $routeVoter-&amp;gt;vote($this-&amp;gt;tokenStorage-&amp;gt;getToken(), $routeName, [RouteVoter::ACCESS_ROUTE]);&lt;br&gt;
            if (VoterInterface::ACCESS_DENIED === $routeDecision) {&lt;br&gt;
                return false;&lt;br&gt;
            } elseif (VoterInterface::ACCESS_GRANTED === $routeDecision) {&lt;br&gt;
                return true;&lt;br&gt;
            }&lt;br&gt;
        }&lt;br&gt;
        if (null !== $permission) {&lt;br&gt;
            // Determine requested access type&lt;br&gt;
            $accessType = $this-&amp;gt;getResourceAccessType($permission);&lt;br&gt;
            if (null === $accessType) {&lt;br&gt;
                return true;&lt;br&gt;
            }&lt;br&gt;
            $resourceVoter = new ResourceVoter($this-&amp;gt;adminUserRightsProvider);&lt;br&gt;
            $resourceDecision = $resourceVoter-&amp;gt;vote($this-&amp;gt;tokenStorage-&amp;gt;getToken(), $permission, [$accessType]);&lt;br&gt;
            if (VoterInterface::ACCESS_DENIED === $resourceDecision) {&lt;br&gt;
                return false;&lt;br&gt;
            } elseif (VoterInterface::ACCESS_GRANTED === $resourceDecision) {&lt;br&gt;
                return true;&lt;br&gt;
            }&lt;br&gt;
            // Try to call Symfony voter&lt;br&gt;
            return $this-&amp;gt;authorizationChecker-&amp;gt;isGranted($permission, $resource);&lt;br&gt;
        }&lt;br&gt;
        return true;&lt;br&gt;
    }&lt;br&gt;
    private function getResourceAccessType(string $permission): ?string&lt;br&gt;
    {&lt;br&gt;
        $type = null;&lt;br&gt;
        if (\preg_match('/\.([a-z]+)$/', $permission, $matches)) {&lt;br&gt;
            $type = $matches[1];&lt;br&gt;
        }&lt;br&gt;
        switch ($type) {&lt;br&gt;
            case ResourceActions::INDEX:&lt;br&gt;
                return ResourceVoter::LIST_RESOURCES;&lt;br&gt;
            case ResourceActions::UPDATE:&lt;br&gt;
                return ResourceVoter::UPDATE_RESOURCE;&lt;br&gt;
            case ResourceActions::CREATE:&lt;br&gt;
                return ResourceVoter::CREATE_RESOURCE;&lt;br&gt;
            case ResourceActions::SHOW:&lt;br&gt;
                return ResourceVoter::SHOW_RESOURCE;&lt;br&gt;
            case ResourceActions::DELETE:&lt;br&gt;
                return ResourceVoter::DELETE_RESOURCE;&lt;br&gt;
            default:&lt;br&gt;
                return null;&lt;br&gt;
        }&lt;br&gt;
    }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is an example of how to do it, but of course there are many other ways. Once more, // TODO&lt;/p&gt;

&lt;p&gt;Once this filter is done, if we are still there, and no boolean has been returned, we are left with the abstain_for. Meaning another voter should now be asked to vote for this access.&lt;/p&gt;

&lt;p&gt;And here comes the SymfonyVoter. The idea here will be to be able to grant access to a resource according to some specifications. Like an admin can edit his profile, but can not edit other admin.&lt;/p&gt;

&lt;p&gt;And since we managed to fetch the resource earlier, it is easy to compare according to custom logic. Here is an example.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;?php&lt;br&gt;
declare(strict_types=1);&lt;br&gt;
namespace App\RightsManagement\Security\Voter\User;&lt;br&gt;
use App\Entity\User\AdminUser;&lt;br&gt;
use LogicException;&lt;br&gt;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;&lt;br&gt;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;&lt;br&gt;
use function in_array;&lt;br&gt;
final class AdminUserVoter extends Voter&lt;br&gt;
{&lt;br&gt;
    public const UPDATE = 'sylius.admin_user.update';&lt;br&gt;
    /**&lt;br&gt;
     * @param string $attribute&lt;br&gt;
     * @param mixed  $subject&lt;br&gt;
     *&lt;br&gt;
     * @return bool&lt;br&gt;
     */&lt;br&gt;
    protected function supports($attribute, $subject): bool&lt;br&gt;
    {&lt;br&gt;
        if (!in_array($attribute, [self::UPDATE])) {&lt;br&gt;
            return false;&lt;br&gt;
        }&lt;br&gt;
        if (!$subject instanceof AdminUser) {&lt;br&gt;
            return false;&lt;br&gt;
        }&lt;br&gt;
        return true;&lt;br&gt;
    }&lt;br&gt;
    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool&lt;br&gt;
    {&lt;br&gt;
        $loggedInUser = $token-&amp;gt;getUser();&lt;br&gt;
        if (!$loggedInUser instanceof AdminUser) {&lt;br&gt;
            return false;&lt;br&gt;
        }&lt;br&gt;
        if ($loggedInUser-&amp;gt;getGroup() === AdminUser::GROUP_SUPER_ADMIN) {&lt;br&gt;
            return true;&lt;br&gt;
        }&lt;br&gt;
        /** @var AdminUser $subjectUser */&lt;br&gt;
        $subjectUser = $subject;&lt;br&gt;
        switch ($attribute) {&lt;br&gt;
            case self::UPDATE:&lt;br&gt;
                return $this-&amp;gt;canUpdate($subjectUser, $loggedInUser);&lt;br&gt;
        }&lt;br&gt;
     throw new LogicException('This code should not be reached.');&lt;br&gt;
    }&lt;br&gt;
    private function canUpdate(AdminUser $subject, AdminUser $loggedInUser): bool&lt;br&gt;
    {&lt;br&gt;
        // Non admin user can only update themselves&lt;br&gt;
        return $subject-&amp;gt;getId() === $loggedInUser-&amp;gt;getId();&lt;br&gt;
    }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And there you go! That means that our new role can perform taxon management, can list products, and edit his profile. Everything else will be forbidden.&lt;/p&gt;

&lt;p&gt;Starting from that, you can easily do any custom mechanism you want inside your Sylius App.&lt;/p&gt;

&lt;p&gt;"What about you?&lt;br&gt;
How are you managing roles inside your application? Feel free to share the article on social media and I’ll join the discussion!&lt;/p&gt;

</description>
      <category>backend</category>
      <category>sylius</category>
      <category>ecommerce</category>
      <category>admin</category>
    </item>
    <item>
      <title>Bulletproof static code analysis for React</title>
      <dc:creator>Locastic</dc:creator>
      <pubDate>Mon, 26 Oct 2020 09:03:52 +0000</pubDate>
      <link>https://dev.to/locastic/bulletproof-static-code-analysis-for-react-am0</link>
      <guid>https://dev.to/locastic/bulletproof-static-code-analysis-for-react-am0</guid>
      <description>&lt;p&gt;First of all, let’s clarify what even is static code analysis.&lt;/p&gt;

&lt;p&gt;Wikipedia says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Static program analysis is the analysis of computer software that is performed without actually executing programs, in contrast with dynamic analysis, which is analysis performed on programs while they are executing. In most cases the analysis is performed on some version of the source code, and in the other cases, some form of the object code.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, static analysis simply parse the code you wrote, validate it against the rules you defined, and gives you immediate feedback whether or not your code meets defined requirements (typos and type errors while you write the code)&lt;/p&gt;

&lt;p&gt;I like the opinion that static code analysis is the first and lowest level of the testing pyramid. Unlike more advanced levels of testing like integration or unit testing, static analysis can be performed against the code without actually running it (no servers or build processes). That alone makes it the fastest and simplest testing tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://testingjavascript.com/"&gt;https://testingjavascript.com/&lt;/a&gt;&lt;br&gt;
The way it works is quite simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compiler finishes the “static analysis” phase&lt;/li&gt;
&lt;li&gt;product is the AST (Abstract syntax tree)&lt;/li&gt;
&lt;li&gt;the tree gets traversed and validated against the defined rules&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Static analysis for React
&lt;/h1&gt;

&lt;p&gt;There are two things I set up by default on every React project I work on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ES Lint – code linter for enforcing certain code style&lt;/li&gt;
&lt;li&gt;Prettier – code formatting tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ES Lint is probably one tool you always want to have present in the codebase. It analyzes the code and quickly finds problems. The fixes it provides are syntax aware which means it will not cause funky bugs. And the best part – you can adjust it according to your needs, which means it’s fully customizable. You can define the set of rules, extend some popular set of rules, etc.&lt;/p&gt;

&lt;p&gt;Pettier on the other hand is used to ensure you have consistent code style trough out the project without having to worry different team members will commit different code styles to the codebase. For example, you want the same indentation, line length, single or double quotes, etc.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;npm install --save-dev eslint prettier&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In order for Prettier to work with ES Lint, prettier-plugin needs to be installed as well:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install --save eslint-plugin-prettier&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ES Lint configuration&lt;/strong&gt;&lt;br&gt;
To configure the ES Lint we can use .eslintrc, a file which would look something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
    "env": {&lt;br&gt;
      "browser": true,&lt;br&gt;
      "es6": true&lt;br&gt;
    },&lt;br&gt;
    "parser": "babel-eslint",&lt;br&gt;
    "extends": ["airbnb", "prettier", "prettier/react"],&lt;br&gt;
    "globals": {&lt;br&gt;
      "Atomics": "readonly",&lt;br&gt;
      "SharedArrayBuffer": "readonly"&lt;br&gt;
    },&lt;br&gt;
    "parserOptions": {&lt;br&gt;
      "ecmaFeatures": {&lt;br&gt;
        "jsx": true,&lt;br&gt;
        "modules": true&lt;br&gt;
      },&lt;br&gt;
      "ecmaVersion": 2018,&lt;br&gt;
      "sourceType": "module"&lt;br&gt;
    },&lt;br&gt;
    "plugins": ["react", "prettier"],&lt;br&gt;
    "rules": {&lt;br&gt;
      "prettier/prettier": "error",&lt;br&gt;
      "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],&lt;br&gt;
      "react/forbid-prop-types": [0, { "forbid": ["any"] }],&lt;br&gt;
      "react/prop-types": 0,&lt;br&gt;
      "react/destructuring-assignment": 0,&lt;br&gt;
      "react/sort-comp": 0,&lt;br&gt;
      "react/no-did-update-set-state": 0,&lt;br&gt;
      "react/jsx-boolean-value": 0,&lt;br&gt;
      "prefer-template": 1,&lt;br&gt;
      "prefer-const": 1,&lt;br&gt;
      "no-unused-vars": 1,&lt;br&gt;
      "import/prefer-default-export": 1,&lt;br&gt;
      "import/no-extraneous-dependencies": 1,&lt;br&gt;
      "import/no-unresolved": 1&lt;br&gt;
    }&lt;br&gt;
  }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Using this file, everything is configurable. Under the rules key, we can change the certain rule to display as a warning, error, or not display at all (disable it). The pluginskey is used to define the set of plugins we want to use (notice the “prettier” plugin we installed before). If you’d like to extend some popular set of ES Lint rules, let’s say Airbnb’s one, you can do that under the extends key. You can find more about configuring the ES Lint on the &lt;a href="https://eslint.org/"&gt;https://eslint.org/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now the linter is all set up, how to run it?&lt;/p&gt;

&lt;p&gt;You can add the following lines to you package.json scripts:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"scripts": {&lt;br&gt;
    "lint" : "eslint ." //to lint all files&lt;br&gt;
    "lint:fix" : "eslint --fix", //to fix all eslint errors&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you’re using the VS Code you can install the ES Lint plugin for it (probably other code editors have it too ).&lt;/p&gt;

&lt;p&gt;The files you don’t want to lint you can ignore using .eslintignore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;dist&lt;/li&gt;
&lt;li&gt;node_modules&lt;/li&gt;
&lt;li&gt;public&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Prettier configuration
&lt;/h1&gt;

&lt;p&gt;It’s worth to mention that Prettier is not as configurable as ES Lint, but it really has all you need for code formatting. We can use .prettierrc file to configure it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{-&lt;br&gt;
    "printWidth": 80,&lt;br&gt;
    "tabWidth": 2,&lt;br&gt;
    "useTabs": true,&lt;br&gt;
    "semi": true,&lt;br&gt;
    "singleQuote": true,&lt;br&gt;
    "trailingComma": "none",&lt;br&gt;
    "bracketSpacing": true,&lt;br&gt;
    "newline-before-return": true,&lt;br&gt;
    "no-duplicate-variable": [true, "check-parameters"],&lt;br&gt;
    "no-var-keyword": true,&lt;br&gt;
    "arrowParens": "avoid",&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can find full set of options available on the &lt;a href="https://prettier.io/docs/en/options.html"&gt;https://prettier.io/docs/en/options.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, if you are using VS Code there is a Prettier plugin available to install but there are also commands to run the code formatting manually:&lt;br&gt;
//package.json&lt;br&gt;
&lt;code&gt;"scripts": {&lt;br&gt;
    "prettier": "prettier --check",&lt;br&gt;
    "prettier:fix": "prettier --write"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For ignoring certain files you can use .prettierignore file (in the same way like .eslintignore).&lt;/p&gt;

&lt;p&gt;And there you go. Everything is set up, you are good to start coding with confidence the ES Lint gives you by checking you don’t make silly mistakes like redeclaring the same variable, not closing bracket, or using something that doesn’t exist, while Prettier will make sure your code is readable and consistent trough out the whole project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bonus
&lt;/h1&gt;

&lt;p&gt;There is one more thing I like to add to make sure the code with limiting errors and unformatted code cannot be committed to the version control provider at all. It’s the Husky, git hooks tool that allows you to run the scripts before commit, push, etc. Let’s take it a bit further with Lint Staged which allows us to check only staged files. The configuration goes like this:&lt;/p&gt;

&lt;p&gt;First, installation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install --save-dev husky lint-staged&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Second, package.json:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"lint-staged": {&lt;br&gt;
            "*.+(js|jsx)": [&lt;br&gt;
      "eslint --fix",&lt;br&gt;
    ],&lt;br&gt;
    ".+(json|css|md)": [&lt;br&gt;
      "prettier --write",&lt;br&gt;
    ]&lt;br&gt;
    },&lt;br&gt;
        "husky": {&lt;br&gt;
       "hooks": {&lt;br&gt;
          "pre-commit": "lint-staged"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s it. Now every time the code is committed hook will execute, validate the files you want to commit and either fix the errors for you or warn you there is some error that can’t be auto fixed.&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>React Native cookie-based authentication</title>
      <dc:creator>Locastic</dc:creator>
      <pubDate>Mon, 03 Aug 2020 08:11:37 +0000</pubDate>
      <link>https://dev.to/locastic/react-native-cookie-based-authentication-1iep</link>
      <guid>https://dev.to/locastic/react-native-cookie-based-authentication-1iep</guid>
      <description>&lt;p&gt;User authentication is a single-handedly most required feature when building a modern web or mobile apps. It allows verifying users, user sessions, and most importantly it provides the base for implementing user authorization (roles and permissions).&lt;/p&gt;

&lt;p&gt;Basically, you develop a login screen and allow the user to input their username/email and appropriate password and send a request to the server. If the server responds positively, that's it. Your user is logged in. But server returned one more thing: some kind of user identification you need to pass together with other requests to access certain data etc. Also, when the user closes the app without logging out, thanks to this we can keep him logged in and skip the login step every time the user opens the app.&lt;/p&gt;

&lt;p&gt;It is either token-based authentication or session-based authentication. This diagram describes the main differences between the two:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TqIcpEtq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/6728ec52-d84b-4ad9-a189-105f8585c86d/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3DAKIAT73L2G45O3KS52Y5%252F20200603%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20200603T093335Z%26X-Amz-Expires%3D86400%26X-Amz-Signature%3Df48eb4156aa961142cec31d0f3c88ba5dccd1fe8b742c32eba7390e45f69ecbf%26X-Amz-SignedHeaders%3Dhost%26response-content-disposition%3Dfilename%2520%253D%2522Untitled.png%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TqIcpEtq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/6728ec52-d84b-4ad9-a189-105f8585c86d/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3DAKIAT73L2G45O3KS52Y5%252F20200603%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20200603T093335Z%26X-Amz-Expires%3D86400%26X-Amz-Signature%3Df48eb4156aa961142cec31d0f3c88ba5dccd1fe8b742c32eba7390e45f69ecbf%26X-Amz-SignedHeaders%3Dhost%26response-content-disposition%3Dfilename%2520%253D%2522Untitled.png%2522" alt="" width="576" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Chart credit: &lt;/em&gt;&lt;a href="https://dzone.com/articles/cookies-vs-tokens-the-definitive-guide"&gt;&lt;em&gt;https://dzone.com/articles/cookies-vs-tokens-the-definitive-guide&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, in cookie-based authentication, after successful login, the server creates the session and return sessionId value as Cookie. Subsequent requests contain that cookie with sessionId which is verified against sessionId on the server to determine if the session is valid.&lt;/p&gt;

&lt;p&gt;On the other hand, we have token-based authentication. After successful login server returns signed token. That token is then usually stored in local storage. Subsequent requests are sent together with the saved token in the Authorization header. The server decodes token and if it is valid, process the request.&lt;/p&gt;

&lt;p&gt;Without further due, like the title states - this article will go through cookie-based authentication in React Native because it is not as straightforward as you may think.&lt;/p&gt;

&lt;h2&gt;The Problem&lt;/h2&gt;

&lt;p&gt;As you know, React Native relies on the native (Android and iOS) APIs written in Java and Objective-C. You may think the cookie usage is as straightforward as using it within the browser but unfortunately, it isn't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L2VMXcUB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://locastic.com/wp-content/uploads/2020/06/Screenshot-2020-06-02-at-10.47.51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L2VMXcUB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://locastic.com/wp-content/uploads/2020/06/Screenshot-2020-06-02-at-10.47.51.png" alt="" class="wp-image-9266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Native networking APIs are saving the cookies by default and it may seem perfectly okay at the beginning, but after some time and few requests made, requests can become inconsistent causing the server to deny the access because the cookies we sent were invalid even though there is nothing wrong with them when they initially were passed along the request.&lt;/p&gt;

&lt;h2&gt;The Solution&lt;/h2&gt;

&lt;p&gt;The first thing that came to my mind is to take the cookie management in to my own hands by simply storing them on the device (eg. Async Storage)&lt;/p&gt;

&lt;p&gt;Now the flow goes like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C9W8ORJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/6058a708-5595-4b5e-ba47-1fd15a58e05e/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3DAKIAT73L2G45O3KS52Y5%252F20200603%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20200603T093825Z%26X-Amz-Expires%3D86400%26X-Amz-Signature%3Ddf7ef8224cdd40e09126714b2928c1aca4ebd6cccecee2263e529f1f344c5c1c%26X-Amz-SignedHeaders%3Dhost%26response-content-disposition%3Dfilename%2520%253D%2522Untitled.png%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C9W8ORJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/6058a708-5595-4b5e-ba47-1fd15a58e05e/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3DAKIAT73L2G45O3KS52Y5%252F20200603%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20200603T093825Z%26X-Amz-Expires%3D86400%26X-Amz-Signature%3Ddf7ef8224cdd40e09126714b2928c1aca4ebd6cccecee2263e529f1f344c5c1c%26X-Amz-SignedHeaders%3Dhost%26response-content-disposition%3Dfilename%2520%253D%2522Untitled.png%2522" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;after successful login, the server responds with the status and cookies&lt;/li&gt;
&lt;li&gt;cookies are saved on the device (Async Storage)&lt;/li&gt;
&lt;li&gt;each subsequent request's Header is populated with the cookie from device storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And I thought this is the final solution. EZ right? But let's see what the flow really looks like now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bDKUDazc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/708df740-6047-43df-9c3e-0a77a77fddfb/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3DAKIAT73L2G45O3KS52Y5%252F20200603%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20200603T093834Z%26X-Amz-Expires%3D86400%26X-Amz-Signature%3D577245ff403416c6851949a88b27365df003dc98dfdf44c9b2213bb3435c10ba%26X-Amz-SignedHeaders%3Dhost%26response-content-disposition%3Dfilename%2520%253D%2522Untitled.png%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bDKUDazc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/708df740-6047-43df-9c3e-0a77a77fddfb/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3DAKIAT73L2G45O3KS52Y5%252F20200603%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20200603T093834Z%26X-Amz-Expires%3D86400%26X-Amz-Signature%3D577245ff403416c6851949a88b27365df003dc98dfdf44c9b2213bb3435c10ba%26X-Amz-SignedHeaders%3Dhost%26response-content-disposition%3Dfilename%2520%253D%2522Untitled.png%2522" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was working fine for a while but then the same problems started to occur and I was at the starting point again. As mentioned above, React Native has its own cookie management and now I implemented my own on top of it. Naturally, native API interfered with my implementation and won every time, overriding the cookie I sent with its own and causing the same problems to appear.&lt;/p&gt;

&lt;p&gt;NOTE: I'm not 100% sure yet this is what is happening on the native side.&lt;/p&gt;

&lt;p&gt;After some research, I stumbled on the &lt;a href="https://github.com/joeferraro/react-native-cookies"&gt;react-native-cookies&lt;/a&gt;. It's a cookie management library for React Native and lets you manage cookies natively. Now there is actually a way to manipulate native cookie management, the approach with storing the cookie on the device can be further improved.&lt;/p&gt;

&lt;p&gt;As already mentioned, native cookie management interfered with the stored cookies. So let's completely remove native cookies and work just with the ones stored on the device. The easiest way would be to just clean up the cookies stored natively.&lt;/p&gt;

&lt;p&gt;That's where the above-mentioned library comes into play:&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code&gt;import CookieManager from 'react-native-cookies'
import AsyncStorage from '@react-native-community/async-storage';

const client = async () =&amp;gt; {
    await CookieManager.clearAll() //clearing cookies stored 
                                       //natively before each 
                                       //request
    const cookie = await AsyncStorage.getItem('cookie')
    return await fetch('api/data', {
        headers: {
            'cookie': cookie
        }
    })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With natively stored cookies cleaned up before each request, it's certain that the only cookies that are passed along the request are the ones stored manually on the device. Using this simple fix there are no more cookies interfering with each other and the main benefit is consistent sessions while using the app.&lt;/p&gt;

&lt;h2&gt;Wrap up&lt;/h2&gt;

&lt;p&gt;I spent a significant amount on time wrestling with this issue because it wasn't so straight forward to figure out what seems to be the problem. I decided to write it down so you don't have to.&lt;/p&gt;

&lt;p&gt;I think it's worth mentioning to say this is not the only possible solution to this problem because the problem itself is not fully investigated.  There are also solutions without using any library which is described in this &lt;a href="https://www.syntx.io/react-native/cookie-management-in-react-native/"&gt;article&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
