File - Using Existing Resources as Boilerplate


In this video we cover off all the elements of the File resource that stay roughly the same (in terms of implementation) as the resources we have already covered before (User and Account).

The reasoning behind this is that once you have an understanding of how the pieces of this system fit together, it is relatively straightforward - dare I say, copy and paste - to add in new resources as needed.

As mentioned in the previous video, File is a nested resource, which means that a File will always belong to an Account, and this can be seen clearly from the URL structure:

/accounts/{accountId}/files/{fileId}

To configure this, we need to add in an extra line to our routing definition. To make this a little easier to manage, I split my routing into two files. The first is the standard routing.yml file that comes out of the box with Symfony, and the second is a routing_api.yml file which I create and then 'link to' using the resource option:

# app/config/routing.yml

api_login_check:
    path: /login

NelmioApiDocBundle:
    resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
    prefix:   "/doc"

api:
    type:     rest
    resource: "routing_api.yml"

And:

# app/config/routing_api.yml

# -- ROOT RESOURCES --
accounts:
    type:     rest
    resource: AppBundle\Controller\AccountsController

users:
    type:     rest
    resource: AppBundle\Controller\UsersController

# -- ACCOUNT CHILDREN --
files:
    type:     rest
    parent:   accounts
    resource: AppBundle\Controller\FilesController

Whilst I don't tend to mix API and 'normal' Symfony builds into the same project, it should be less cumbersome to do so if your routing structure (and other config) is separated out.

Service Definitions

Whilst I said that in this video we talk about what stays the same, that doesn't mean there isn't a lot of new stuff to cover. There is new code and config here, but it is largely very similar to what we have already configured.

The best example of this is in services.yml, which sees almost every 'section' have something new added in. Let's take a closer look:

# app/config/services.yml

    # -- DATA TRANSFORMERS --
    crv.data_transformer.account_data_transformer:
        class: AppBundle\DataTransformer\AccountDataTransformer

    crv.data_transformer.file_data_transformer:
        class: AppBundle\DataTransformer\FileDataTransformer

We covered off the reasoning and implementation behind Data Transformers in an earlier video, so I won't go into the specifics here.

Generally for each new resource, the existing service definitions (and associated classes) will need creating for the new resource also. We are going to allow files to be created and updated, so we need a way of converting between the data sent in, and the entities which Doctrine can persist. This is handled by our Data Transformers, so we need to add in a Data Transformer for File, just like we did for Account.

This repeats itself for each of the other type of services we have already defined:

# app/config/services.yml

    # -- FORM HANDLER --
    crv.form.handler.account_form_handler:
        class: AppBundle\Form\Handler\FormHandler
        arguments:
            - "@form.factory"
            - "@crv.form.type.account"

    crv.form.handler.file_form_handler:
        class: AppBundle\Form\Handler\FormHandler
        arguments:
            - "@form.factory"
            - "@crv.form.type.file"

    # -- REPOSITORY --
    crv.repository.doctrine_account_repository:
        class: AppBundle\Repository\Doctrine\DoctrineAccountRepository
        arguments:
            - "@crv.repository.common_doctrine_repository"
            - "@crv.doctrine_entity_repository.account"

    crv.repository.doctrine_file_repository:
        class: AppBundle\Repository\Doctrine\DoctrineFileRepository
        arguments:
            - "@crv.repository.common_doctrine_repository"
            - "@crv.doctrine_entity_repository.file"

    # etc

And as mentioned, this means each of the existing classes - e.g. DoctrineAccountRepository - can easily be copy / pasted, have the methods re-written, and be rather quickly re-used. This effectively becomes your boilerplate. Personally I find this is a huge help to me when working on a project. If the file patterns repeat themselves, you can focus on writing the application rather than structuring the system.

An example of this can be illustrated with the Data Transformers from earlier. If we look at the 'shape' of these files without their implementations, you will hopefully see what I am aiming for:

<?php

// src/AppBundle/DataTransformer/AccountDataTransformer.php

namespace AppBundle\DataTransformer;

use AppBundle\DTO\AccountDTO;
use AppBundle\Model\AccountInterface;
use AppBundle\Model\UserInterface;

class AccountDataTransformer
{
    public function convertToDTO(AccountInterface $account)
    {
    }

    public function updateFromDTO(AccountInterface $account, AccountDTO $dto)
    {
    }
}

Creating an equivalent FileDataTransformer becomes trivial:

<?php

// src/AppBundle/DataTransformer/FileDataTransformer.php

namespace AppBundle\DataTransformer;

use AppBundle\DTO\FileDTO;
use AppBundle\Model\FileInterface;

class FileDataTransformer
{
    /**
     * @param FileInterface $file
     * @return FileDTO
     */
    public function convertToDTO(FileInterface $file)
    {
    }

    /**
     * @param FileInterface $file
     * @param FileDTO $dto
     * @return FileInterface
     */
    public function updateFromDTO(FileInterface $file, FileDTO $dto)
    {
    }
}

The implementations differ, but the 'shape' stays the same.

Unfortunately PHP interfaces won't allow generics so we can't create a single common interface here. It would be good practice to create an interface per implementation anyway, which I haven't done here. It's unlikely at this stage that I would create multiple implementations of FileDataTransformer, but even so, other code that relies on this will ultimately end up relying on a concrete implementation - which is not so good, Al.

Code For This Course

Get the code for this course.

Episodes