Gedmo Translations in Symfony 4

Today I’ve had to install Gedmo Translations into a Symfony 4 app and I had some trouble, so after fixing them problems I thought of writing them down in case someone else can benefit from it 🙂

Let’s start fromt he begginig.

Create a Symfony 4 project

Please use docker! 🙂 More info here in the Docker for Symfony 4 post.

Here’s what my docker.compose.yml looks like

#docker.compose.yml
version: "3.1"

volumes:
    db-data:

services:
    mysql:
      image: mysql:5.6
      container_name: ${PROJECT_NAME}-mysql
      working_dir: /application
      volumes:
        - db-data:/application
      environment:
        - MYSQL_ROOT_PASSWORD=docker_root
        - MYSQL_DATABASE=gedmoapp_db
        - MYSQL_USER=gedmoapp_user
        - MYSQL_PASSWORD=gedmoapp_pw
      ports:
        - "8306:3306"

    webserver:
      image: nginx:alpine
      container_name: ${PROJECT_NAME}-webserver
      working_dir: /application
      volumes:
        - .:/application
        - ./docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      ports:
        - "8000:80"

    php-fpm:
      build: docker/php-fpm
      container_name: ${PROJECT_NAME}-php-fpm
      working_dir: /application
      volumes:
        - .:/application
        - ./docker/php-fpm/php-ini-overrides.ini:/etc/php/7.2/fpm/conf.d/99-overrides.ini

Installation of Gedmo’s bundle

composer require stof/doctrine-extensions-bundle

Configuration

Now we need to update our configuration file, so in the doctrine.yaml, under the doctrine key, we should go from this

#doctrine.yaml
doctrine:
#...
    orm:
        auto_generate_proxy_classes: '%kernel.debug%'
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

To this

#doctrine.yaml
doctrine:
#...
	orm:
        auto_generate_proxy_classes: '%kernel.debug%'
#        naming_strategy: doctrine.orm.naming_strategy.underscore
#        auto_mapping: true
        entity_managers:
            default:
#                connection: default
                naming_strategy: doctrine.orm.naming_strategy.underscore
                auto_mapping: true
                mappings:
                    App:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity'
                        prefix: 'App\Entity'
                        alias: App
                    gedmo_translatable:
                        type: annotation
                        prefix: Gedmo\Translatable\Entity
                        dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity"
                        alias: GedmoTranslatable # (optional) it will default to the name set for the mapping
                        is_bundle: false
                    gedmo_translator:
                        type: annotation
                        prefix: Gedmo\Translator\Entity
                        dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translator/Entity"
                        alias: GedmoTranslator # (optional) it will default to the name set for the mapping
                        is_bundle: false
#                    gedmo_loggable:
#                        type: annotation
#                        prefix: Gedmo\Loggable\Entity
#                        dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Loggable/Entity"
#                        alias: GedmoLoggable # (optional) it will default to the name set for the mappingmapping
#                        is_bundle: false
                    gedmo_tree:
                        type: annotation
                        prefix: Gedmo\Tree\Entity
                        dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity"
                        alias: GedmoTree # (optional) it will default to the name set for the mapping
                        is_bundle: false

Also, in our stof_doctrine_extensions.yaml, let’s add the following configuration for the doctrine extensions.

#stof_doctrine_extensions.yaml
stof_doctrine_extensions:
    default_locale: en_US
    orm:
        default:
            tree: true
            translatable: true
            sluggable: true

Now, if we try to update our schema

php bin/console doc:sch:update --dump-sql
The following SQL statements will be executed:

     CREATE TABLE ext_translations (id INT AUTO_INCREMENT NOT NULL, locale VARCHAR(8) NOT NULL, object_class VARCHAR(255) NOT NULL, field VARCHAR(32) NOT NULL, foreign_key VARCHAR(64) NOT NULL, content LONGTEXT DEFAULT NULL, INDEX translations_lookup_idx (locale, object_class, foreign_key), UNIQUE INDEX lookup_unique_idx (locale, object_class, field, foreign_key), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT = DYNAMIC;

Looks about right, so let’s hit it!

php bin/console doc:sch:update --force

And we get the following error

Updating database schema...


In AbstractMySQLDriver.php line 126:
                                                                                                                                
  An exception occurred while executing 'ALTER TABLE ext_translations CHANGE object_class object_class VARCHAR(255) NOT NULL':  
                                                                                                                                
  SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes               
                                                                                                                                

In PDOConnection.php line 109:
                                                                                                                   
  SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes  
                                                                                                                   

In PDOConnection.php line 107:
                                                                                                                   
  SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes

Now, after looking for this issue with Symfony and Gedmo Translations in the internet, I found this issue about Doctrine and this other one, and the fix here.

As davidbehler and javiereguiluz explain in the posts,

the cause of this is that Symfony advises you to use utf8mb4_general_ci/utf8mb4 as collation/charset for your database. utf8mb4 takes 4 bytes per char, meaning a 255 char field needs 1020 bytes for an index.

And so it seems that MySql 5.6 has a max key length of 767 bytes, so that leaves us with a couple of options:

1. Decrease the field length to 191. However, we would have to override Gedmo’s translations bundle, as it uses 255 char fields everywhere..

2. We could upgrade to mysql 5.7

#docker-compose.yml
services:
    mysql:
      image: mysql:5.7
#...

So if you opt for option two, it works gracefully. Hope this helps, happy coding! 🙂

Gedmo’s Translations Documentation

StofDoctrineExensionBundle in Symfony’s official website.
Doctrine’s extensions GitHub doc.
Translatable’s GitHub doc.

Running PHPUnit tests before git push

What usually happens

“Great, I’ve just finished this new feature and it works perfectly, let’s merge it with the rest of the code so the team can now benefit from it..”

git add .
git commit -m "-blablabla"
git flow feature finish "blabla-feature-name"
git push origin develop

“Oops, I forgot to pass tests after closing the feature branch…”

./vendor/bin/simple-phpunit

...F
FAILURES!
Tests:4... Failures: 1

We’ve all done it before, pushed code that didn’t pass tests…

Pre-Push git hook solution

On this blog post I’ll show you how to set a pre-push script, so every time you push code to your remote repository (develop or master only for this example), it’ll try to run your tests. And as a result, it will only push the code if tests pass.

This is intended to be like a safety net, specially if you’re working with a team and your code needs to be tested before being pushed!

This is what the script file looks like:

#!/usr/bin/env php

< ?php

/**
 * ===========================
 * This hook will only allow git push if your all test pass
 * To activate this hook, you need to place this file in the .git/hooks folder
 * ===========================
 **/


$gitBranch = shell_exec('git branch | grep \*');
$branchName = trim(str_replace('*', '', $gitBranch));
if( $branchName !== 'develop' && $branchName !== 'master' ) {
    echo "You're not in master or develop, so hook tests won't activate.." . PHP_EOL;
    exit(0);
}

echo "Running tests.. ";
exec('./vendor/bin/simple-phpunit', $output, $returnCode);
if ($returnCode !== 0) {
  // Show full output
  echo PHP_EOL . implode($output, PHP_EOL) . PHP_EOL;
  echo "Cannot push changes untill tests are OK.." . PHP_EOL;
  exit(1);
}

// Show summary (last line)
echo array_pop($output) . PHP_EOL;
exit(0);
Note1: Notice the start of script tag < ?php . You will need to remove the white space so PHP interprets the code.
Note2: The name of this file must be pre-push and must be located inside the .git/hooks folder in your project. This is because git will look for that actual file before pushing.

So if we dive into the code, it's pretty simple.

The first block looks in what branch you're located. I've just set up this script to run tests when you try and push to develop or master. You can modify/delete this part if you want a different behaviour.

The second block is the actual running of the tests. This only runs if you're located in develop or master.

As you can see, if return code after running tests is different than 0, AKA a tests failed, it'll exit the script with an exit(1), letting git know it must not push the code.

If all test pass, it'll exit with an exit(0) and so, the code will be pushed.

And that's it! Simple right?

There are other git hooks that might be useful for you. Try looking at the .git/hooks folder for samples (pre-commit, pre-rebase, pre-receive...) and let me know if you use any other ones!

Cheers and safe coding! 🙂

From Symfony to Django

Lately I’ve been playing with Django (in a real project, not tutorials) and all I have to say is… Oh My God!! I’m so impressed with Django’s clarity, speed of development and “cleanliness”, that I’m even planning for it to become my main ‘go-to’ framework! Don’t get me wrong, I’ve been working with Symfony for a few years and it’s still going to be the main framework I use for PHP projects, and probably the most comfortable I’ll feel working with for a while, but right now Django is like a new toy and I’m really excited to share this with you!

Furthermore, Django seems to perform far better in performance tests (less CPU resources, faster response…) than Symfony. Check here for more info on this.

This blog post is not a ‘Django getting started guide’, there’s plenty of those online already. Please visit Django’s oficial page for more info on this.

This blog post is for Symfony developers who wish to try Django out and are unaware of the Django project structure, file structure, etc. I’m going to go through the basics so you get a feel of what it’s like to code in Django and hopefully you feel comfortable enough to give it a go 🙂

Routing

Routing files are the ones which have mapped the routes for our application. Symfony and Django alike will try to match one of these routes to handle it.

Symfony annotations in controller

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

/**
 * Displays a form to edit an existing customer entity.
 *
 * @Route("/{id}/edit", name="app_customer_edit")
 */

Symfony YAML: routing.yml

app_customer_edit:
    path:     /{id}/edit
    defaults: { _controller: AppBundle:Customer:edit }

In Symfony you can include specific routing of a bundle like so:

app:
    resource:   "@AppBundle/Resources/config/routing.yml"
    prefix:     /

Django: urls.py

Django routing in a config file called urls.py

from app.views import customer

urlpatterns = [
    url(r'^(?P\d+)/edit$', customer.update_customer, name='customer_edit'),
]

You can also include specific routing in Django

urlpatterns = [
    url(r'^customer/', include('app.urls.customer')),
]

Controllers

Once matched a certain route, Controllers are the ones that receive the request and return a response, with whatever logic you may need in between, using services and so on.

Symfony: CustomerController.php

A Controller in Symfony, which has the action that will fire if the route matches

/**
 * Customer controller.
 *
 * @Route("customer")
 */
class CustomerController extends CoreController
{

    /**
     * Displays a form to edit an existing customer entity.
     *
     * @Route("/{id}/edit", name="app_customer_edit")
     */
    public function editAction(Request $request, Customer $customer)
    {
    
        //custom logic
    
        return $this->render('AppBundle:Customer:edit.html.twig', array(
                'param1' => $param1,
                //...
            ));
    }

}

Django: views.py

In Django, the Controller is called View, so the views.py will have the function we specified in the urls.py

def update_customer(request, pk):

    //custom logic

    return render(request, 'customer/edit.html', {
        'param1': param1,
    })

Template rendering

The templating engine is very very similar, so you won’t have any trouble adapting to the new one.

Symfony: edit.html.twig

Twig is the template engine in Symfony. The Resources/views folder in Symfony which contains all the twigs.

For instance, to create an anchor for a Symfony route in twig:

< a href="{{ path('app_customer_edit', { 'id': customer.id }) }}"> Edit the customer < /a>

Django: edit.html

Django uses it’s own templating engine, and the folder containing all the html files is called templates. Code looks very similar.

< a href="{% url "customer_edit" customer.id %}"> Edit the Customer < /a>

Static files

Symfony: public/

The Resources/public folder in Symfony which contains all the js/css files for later deployment.

To load some of those files in the template in Twig

< link rel="stylesheet" href="{{ asset('bundles/app/vendor/bootstrap/css/bootstrap.min.css') }}">
< script type="text/javascript" src="{{ asset('bundles/app/vendor/bootstrap/js/bootstrap.min.js') }}">

Django: static/

In Django, these files are gathered in a folder called static.

{% load static %}

< link rel="stylesheet" href="{% static "vendor/bootstrap/dist/css/bootstrap.min.css" %}">
< script src="{% static "vendor/bootstrap/dist/js/bootstrap.min.js" %}">

Entities

Entities are mapped models that translate into database tables. The idea is to abstract the database completely, so you can either use Mysql, Mongo, PostgreSql,… totally independent from the code.

Symfony: Customer.php

Using Doctrine, the Customer entity under the Entity folder in Symfony would look something like this:

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\Table(name="app_customer")
 */
class Customer
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=50, nullable=true)
     */
    protected $name;

    /**
     * @var string
     *
     * @ORM\Column(name="surname", type="string", length=80, nullable=true)
     */
    protected $surname;

    /**
     * @var Address
     *
     * @ORM\OneToOne(targetEntity="Address", cascade={"persist"})
     * @ORM\JoinColumn(nullable=true)
     */
    protected $address;


    //more attributes...


	/**
     * @return string
     */
    public function __toString()
    {
        return trim((string) $this->getName(). ' '.  $this->getSurname());
    }
    
    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Customer
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set surname
     *
     * @param string $surname
     *
     * @return Customer
     */
    public function setSurname($surname)
    {
        $this->surname = $surname;

        return $this;
    }

    /**
     * Get surname
     *
     * @return string
     */
    public function getSurname()
    {
        return $this->surname;
    }

    /**
     * Set address
     *
     * @param \AppBundle\Entity\Address $address
     *
     * @return Customer
     */
    public function setAddress(\AppBundle\Entity\Address $address = null)
    {
        $this->address = $address;

        return $this;
    }

    /**
     * Get address
     *
     * @return \AppBundle\Entity\Address
     */
    public function getAddress()
    {
        return $this->address;
    }

    //more getters and setters...

Django: models.py

In Django, much slimmer. No need of any getters and setters, as it has it’s own model API that handles that.

from django.db import models
from app.models.address import Address


class Customer(models.Model):
    name = models.CharField('Name', max_length=50)
    surnames = models.CharField('Surnames', max_length=80)
    address = models.OneToOneField(
        Address,
        on_delete=models.CASCADE
    )

    def __str__(self):
    	return self.name + ' ' + self.surnames

Forms

Symfony: FormType.php

In Symfony we’ll use a FormType, which usually extends from AbstractType. We also need to tell Symfony what class it’s mapping through the configureOptions method.

use Symfony\Component\Form\AbstractType;

class CustomerType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', null, [
                'label' => 'Nom',
            ])
            ->add('items', CollectionType::class, [
                'entry_type' => CustomerItemType::class,
                'allow_add'    => true,
                'allow_delete' => true,
                'by_reference' => false
            ])
            ...
         ;
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => Customer::class,
            'cascade_validation' => true,
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_customer';
    }
}

To build the form from the controller we would use something like this

$form = $this->createForm(CustomerType::class, $customer);

This basically calls the form builder service and builds an CustomerType form with the $customer instance.

Django: forms.py

In Django it’s much simpler! We also need to tell Django what model class it’s mapping through the Meta assignment.

from django import forms

class CustomerForm(forms.ModelForm):

    class Meta:
        model = Customer
        exclude = ('',)

To build the form from the view we would use something like this

form_customer = CustomerForm(request.POST, instance=customer_inst)

Same as Symfony, this will create the form with the customer_inst instance.

ORM – Object Relational Manager

The ORM allows us to access objects from the database in a ‘object manner’, so we don’t need to worry how to access that DDBB, it’s totally independent.

Symfony: Doctrine

In Symfony we use Doctrine, so let’s find an Customer given a request id

public function editAction(Request $request)
{
	$em = $this->getDoctrine()->getManager();
	$customer = $em->getRepository('AppBundle:Customer')->find($request->get('id'));
	$customers = $em->getRepository('AppBundle:Customer')->findAll();
	$address = $customer->getAddress();

	// Find an customer depending on a address
	$customerInst = $em->getRepository('AppBundle:Customer')->findOneByAddress($addressInst);
}

Django: API

In Django it uses it’s own API.

def update_customer(request, pk):
    customer = Customer.objects.get(pk=your_object_id)
    customers = Customer.objects.all()
    address = customer.address

    # Find an customer depending on a address
    customer_inst = Customer.objects.get(address=address_inst)

Summary

I’m not saying Django is better than Symfony or the other way round. Each and every framework/technology has it’s place, and as developers, I believe we should be open minded and have as many tools as possible under the belt. So I strongly recommend every Symfony dev to try Django out if you haven’t done yet. I assure you that you’ll love everything about it, even the great admin it gives you out of the box 🙂

The new era of micro frameworks, welcome Symfony 4

Whoop Whoop! Symfony 3.4 and Symfony 4 were released a couple days ago with lots of hype around it!

But what is really different this time from other Symfony versions? Let’s have a look…

Decoupling components

First off, conceptually, Symfony framework is moving towards a more decoupled structure. You probably noticed that instead of creating a new project through the symfony installer, you now use the following command.

composer create-project symfony/skeleton your-project-name

 
The reason behind this is that in earlier versions, when creating a symfony project, you were installing lots of dependencies and components that you may or may not be using in your application.

The idea now is creating a skeleton type project, and then installing all the components we need for that project seperately. This allows the programer complete freedom to be using whatever component he or she needs.

This in turn means that each Symfony application you code might use different components, even if they were created by Symfony, and therefore you do not need to install all of them every time, which makes each project more light weight!

For example, an API project might not use Twig, so there’s no need to install it in the vendor directory right?

Or I might just install the command component because I need to write some php scripts and I need a quick wrapper to organise my code.

Neither of the projects above would need twig, or form, or entities, or orm… you get the point.

I still remember the days when you had one big bloated Symfony framework installed, and many apps inside it. Inside each app, there were bundles.

After that, it evolved into one app per symfony installation, and each app had it’s different bundles.

It’s now time for the bundle less symfony. Best practices now say that you should have one skeleton per project, and following the decoupling of components, each project will have just the component it needs. This is just great, because every project will now have the underlying code that is really necessary, and not a bloated version of all the Symfony components together.

Having said this, there’s a few components I use 99% of the time when creating websites, so I created this small wrapper script so it’s easy for me to get them all, feel free to grab it and add/remove at your taste :). I call it ‘symfony-create-project.sh’

#!/bin/bash
# This script is a wrapper for creating a skeleton and installing the basic components I use most of the time
# To use it: bash symfony-create-project name-of-your-project

if [ "$1" != "" ]; then
	composer create-project symfony/skeleton "$1"
	cd "$1"
	composer require annotations
	composer require --dev profiler
	composer require twig
	composer require orm
	composer require form
	composer require form validator
else
	echo "parameter expected: name-of-your-project"
fi

Directory Structure

First thing you notice is the disappearance of the web folder. You now have the public folder instead. Inside it we can also see that app.php and app_dev.php have disappeared. Instead, we now have a more default index.php. The environment will be controlled by a .env file in the root directory of the project.

Second of all, the whole Resources folder inside src/ has disapeard as well, and all of it’s contents are in different places. The config files (routing.yml, services.yml…) are in the config/ folder in the root directory. This makes sense now since we have bundle-less applications. All of the twig files have also moved to a general templates/ folder in the root directory.

Now the src/ folder is just for php code, which makes much more sense if you work on a team with frontend and backend dev’s, each of them can now focus on their own folder.

Autoconfiguring and Autowiring

This is my absolute favourite.

Let me just copy this with the comments, as it explains everything really well.

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.

Let’s dive into the code.

So I created a new service class,

<?php

namespace App\Utils;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;

class NumberGenerator
{
    public function __construct()
    {
    }

    public function getRandomNumber()
    {
        return  mt_rand(0, 100);
    }
}

Injected into the action,

<?php

namespace App\Controller;

use App\Utils\NumberGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends Controller
{
    /**
     * @Route("/default/home")
     */
    public function home(NumberGenerator $numberGenerator)
    {
        return $this->render('default/home.html.twig', array(
            'number' => $numberGenerator->getRandomNumber(),
        ));
    }
}

and BANG, it worked like expected.

Let’s recapitulate here… before, we had to create the service, add it in the dependency injection config file, and then get it from the container.

What we just did in Symfony4 is create the service and inject it in the action we needed it. No configuration, no getting… just injection. This REALLY speeds up the process, enough configuration and more coding!!

However, if you don’t want to inject your service in the action, you can still use the container with the get function as we did in the past, although best practices advise not to.

Firstly,


composer require dependency-injection

Secondly, we need to set the service to public.

services:

    App\Utils\NumberGenerator:
        public: true

You can now get the service from the container

...
return $this->render('default/home.html.twig', array(
            'number' => $this->get(NumberGenerator::class)->getRandomNumber(),
        ));
...

There’s another component/bundle, which although is not super necessary, makes your life much much easier: Yup, you guessed it, it’s the Maker Bundle

composer require maker-bundle

This components creates the skeleton files for you (controllers, entities, forms,… etc), for example

bin/console make:controller CarController

More info on the maker bundle here.
More info on all the different component recipes here.

Conclusion

I just love how Symfony keeps giving more and more control to the developer.

Lots uf us (me included) know that changing and evolving things that we’re used to doing is sometimes painful… but after playing with it a little bit, I see this upgrade as a great step forward for better code, better standards and better readability.

So thank you to the Symfony team and everyone who contributed for making this happen!

Happy coding!!

Namespaces and organizing business logic services in Symfony

I want to talk about namespacing services in Symfony, specifically Symfony3.

These are exciting times, Symfony 4 is just round the corner – coming out on November 30th – so this blog post might be irrelevant soon! Nevertheless, concepts are still the same so let´s get into it!

Lately, talking with my team in SlowCode, we defined a common way of defining services.

First rule – using a folder for logic services.

Any service which provides logic to the app would be inside a App\Service folder. This way everything is tidy, and all developers in the team know where to find them.

Second rule – using a folder for the domain name

The next layer is the domain name. This is again, to provide order. You might not think so, but when you end up with 8 domain names, and 2-3 services in each one, then things can get uggly if it’s not tidy 🙂

So for instance, let’s have a service that is related to Stock called StockAvailability, the service would end up with the namespace the folder AppBundle\Service\Stock\StockAvailability

Third rule – using . for folder separation and _ for word separation

The id of the service has to be separated by . when you enter into a new folder, and separated by _ when it’s more than one word.

So in the previous example, we would define the whole thing like so:

services:

    app.service.stock.stock_availability:
        class: AppBundle\Service\Stock\StockAvailability
        arguments:
            - '@doctrine.orm.entity_manager'
            ...

Lately in Symfony 3.3, a new way of defining services was brought up.

It´s now a good practice to define the id of the service with the full qualified name. So, instead of defining it like we did before, we would define it like so:

services:

    AppBundle\Service\Stock\StockAvailability:
    	public: true
        arguments:
            - '@doctrine.orm.entity_manager'
            ...

Declared like this, you can still get the service from the service container (with the new id of course, the full class name)

use AppBundle\Service\Stock\StockAvailability

public function fooAction(Request $request)
{
    // before Symfony 3.3, you would get it like so
    // $stockService = $this->get('app.service.stock.stock_availability');

    // in Symfony 3.3, you can get it like so
    // This is only available if you defined your service as public
    $stockService = $this->get(StockAvailability::class);
}

As Symfony’s official page point out, it’s a good practice to define your services as private, not public, and then inject any service you may need in the action inside the controller, instead of getting it from the service container (similar to the dependency injection inside services), for instance

use AppBundle\Service\Stock\StockAvailability

public function fooAction(Request $request, StockAvailability $stockService) { 
    // now we have it injected into our variable $stockService 
    // so we don't need to get it from the container 
} 

So first off, I think the id with the full class name instead of an invented nomenclature is a good thing. At least there will be no more confusion amongst different devs from the team.

About private/public services. I understand where Symfony is going, and I think restricting by ‘injecting’ instead of ‘getting’ makes code more robust, and probably more readable in the end. However, I still think there’s an upside on how things were prior to 3.3 version. Getting services from the container is VERY useful, and provices flexibility and speed.

I think that since you can still define public services, that’s what I’ll keep doing… what will you do?

More info here:
Symfony service container
Symfony 3.3 best practices
Symfony class for service id

Symfony2 PHPUnit testing database data

Today I had to test some complex algorithm logic we’re writing for a client app. The algorithm I want to test interacts with the DDBB several times through query builders and repositories, so it’s necessary to test this database access, as we need to know the sql queries give the right data also.

It’s the first time I set up a test environment (I worked with Unit Testing before, but never set up the env from scratch). So after reading a bit of doc from the internet, I set up the unit test environment, then wrote some tests that interacted with a test database and test fixtures.

I’ve put together a bit of guide for anyone wanting to get started on this.

Step 1 – Installing doctrine’s fixtures bundle.

First run the composer require

composer require --dev doctrine/doctrine-fixtures-bundle

Once your composer json and lock have updated, it’s time to install it in our Kernel.

// app/AppKernel.php

// ...
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
    // ...
    $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
}

Step 2 – Setting a separate database for testing.

Add these lines on your config_test.yml file.

#config_test.yml

...

# Doctrine Configuration
doctrine:
    dbal:
        driver:   "%test_database_driver%"
        host:     "%test_database_host%"
        port:     "%test_database_port%"
        dbname:   "%test_database_name%"
        user:     "%test_database_user%"
        password: "%test_database_password%"
        # Workaround for DBAL 2.5 auto-detect -> with server_version allows to doctrine:database:create
        server_version: 5.6 # your database server version here

This will point to the new test database.

Then add the data in parameters.yml (don’t forget parameters.yml.dist for future installs)


# This file is auto-generated during the composer install
parameters:

    ...
    test_database_driver: pdo_mysql
    test_database_host: localhost
    test_database_port: null
    test_database_name: test_db_name
    test_database_user: test_db_user
    test_database_password: test_db_pw

Step 3 – Creating a TestCase that we will extend when ddbb fixtures are needed.

This is useful for reusing code, instead of writing it on every file. It also makes all the fixture tests more consistent.


namespace Tests\AppBundle\DataFixtures;

use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;

class DataFixtureTestCase extends WebTestCase
{
    /** @var  Application $application */
    protected static $application;

    /** @var  Client $client */
    protected $client;
    
    /** @var  ContainerInterface $container */
    protected $container;

    /** @var  EntityManager $entityManager */
    protected $entityManager;

    /**
     * {@inheritDoc}
     */
    public function setUp()
    {
        self::runCommand('doctrine:database:drop --force');
        self::runCommand('doctrine:database:create');
        self::runCommand('doctrine:schema:create');
        self::runCommand('doctrine:fixtures:load --append --no-interaction --fixtures=tests/AppBundle/DataFixtures/ORM');

        $this->client = static::createClient();
        $this->container = $this->client->getContainer();
        $this->entityManager = $this->container->get('doctrine.orm.entity_manager');

        parent::setUp();
    }

    protected static function runCommand($command)
    {
        $command = sprintf('%s --quiet', $command);

        return self::getApplication()->run(new StringInput($command));
    }

    protected static function getApplication()
    {
        if (null === self::$application) {
            $client = static::createClient();

            self::$application = new Application($client->getKernel());
            self::$application->setAutoExit(false);
        }

        return self::$application;
    }
    
    /**
     * {@inheritDoc}
     */
    protected function tearDown()
    {
        self::runCommand('doctrine:database:drop --force');

        parent::tearDown();

        $this->entityManager->close();
        $this->entityManager = null; // avoid memory leaks
    }
}

Most of the code is taken from StackOverflow.

This will create the database and install the fixtures on every test that extends this TestCase. This is important as you want a consistent database with the same data every time!

It’s also important to pass the parameter

--fixtures=tests/AppBundle/DataFixtures/ORM'

This way you can set up all your test fixtures separate from your app fixtures, which will stay clean.

Step 4 – Creating the unit, functional and integration tests

After creating your test fixtures, now you can test your services logic easily by extending the TestCase we just created, like so.

class FooTest extends DataFixtureTestCase
{
    protected $fooService;

    /**
     * {@inheritDoc}
     */
    public function setUp()
    {
        parent::setUp();
        $this->fooService = $this->container->get('app.service.foo');
    }

    public function testFooTrue()
    {

        ... 
        //custom logic
        ... 

        $this->assertEquals(true, $value);
    }

  
}

Now you just need to run the phpunit command like so

phpunit tests/

And that’s all you need! Happy testing! 🙂

PS: This is a very easy way to get started. For more complex testing environment settings, you can try LiipFunctionalTestBundle

Deploy on OVH shared hosting with different PHP versions

Recently we’ve been working on a Symfony project that needed to be deployed on a shared hosting by OVH.

This server was running PHP 5.4 and we could not upgrade it because they had some legacy projects that needed to remain..

So after banging our head quite a few times, we found out that you could create a configuration file so that OVH server reads and reconfigures the settings of the server for that project/folder only.

The file must be called .ovhconfig and it must be on the target folder the DNS points to, so in our case being a Symfony project it should be in the /web folder

app.engine=php
app.engine.version=5.6
http.firewall=none
environment=production
container.image=stable

PHP 5.6 and PHP 7 on same Mac with Liip

So the other day I wanted to upgrade to php7 but didn’t want to completely remove php56 as I still have legacy projects to maintain and I’m not ‘dockerized’ yet..

You can install php with liip here.

Liip doesn’t overwrite Apple’s php binaries, it installs it under a php5 folder under /usr/local/php5 and then creates the link.

All you now need is this handy bash script to switch from one php version to the other.

#!/bin/bash

#usage:
#./switch-php.sh 
#./switch-php.sh php5
#./switch-php.sh php7

#activate php56
if [ "$1" == "php5" ]; then
   sudo rm /usr/local/php5
   sudo ln -s /usr/local/php5-5.6.29-20170114-210819 /usr/local/php5
   sudo pkill php-fpm && sudo php-fpm
   echo "activated php5;"
   sudo apachectl restart
elif [ "$1" == "php7" ]; then
   sudo rm /usr/local/php5
   sudo ln -s /usr/local/php5-7.1.9-20170914-100859 /usr/local/php5
   sudo pkill php-fpm && sudo php-fpm
   echo "activated php7;"
   sudo apachectl restart
else
   echo "parameter expected: php5 | php7"
fi

PS: You’ll have to obviously adapt the version to the one you installed!