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:


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

    path: /login

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

    type:     rest
    resource: "routing_api.yml"


# app/config/routing_api.yml

    type:     rest
    resource: AppBundle\Controller\AccountsController

    type:     rest
    resource: AppBundle\Controller\UsersController

    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

        class: AppBundle\DataTransformer\AccountDataTransformer

        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 --
        class: AppBundle\Form\Handler\FormHandler
            - "@form.factory"
            - "@crv.form.type.account"

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

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

        class: AppBundle\Repository\Doctrine\DoctrineFileRepository
            - "@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:


// 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:


// 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.


# Title Duration
1 Project Introduction 17:13
2 Setting Up Our Development Environment 05:08
3 Installing Symfony 3, Behat, and more 13:53
4 User Feature - Part 1 17:48
5 User Feature - Part 2 07:51
6 Talking English To Your Computer 11:06
7 Teaching Your Database To Forget 07:43
8 Creating User Data From Behat Background - Part 1 14:44
9 Creating User Data From Behat Background - Part 2 11:33
10 Creating A Custom RestApiContext 17:45
11 Our First Passing Behat User Scenario 12:01
12 Our Next Passing Step 13:11
13 Securing Our User Endpoint - Part 1 17:18
14 Securing Our User Endpoint - Part 2 24:22
15 Securing Our User Endpoint - Part 3 24:47
16 Log In To A Symfony API With JWTs (LexikJWTAuthenticationBundle) 11:02
17 Implementing PATCH for Users 18:17
18 Improving our API User Experience 13:59
19 GET a Collection of Accounts 12:15
20 POSTing in New Accounts 14:35
21 PUT and PATCH for Accounts 12:15
22 How To DELETE Existing Accounts 05:11
23 File Feature Overview 11:40
24 File - Using Existing Resources as Boilerplate 15:17
25 File POST 14:54
26 Fixing A Bug In POST Guided By Behat 12:51
27 Wrapping Up With File DELETE 07:48