Symfony Archives - Cleverti https://www.cleverti.com/tag/symfony/ B2B Nearshore Software Provider. Software Development and QA & Testing Tue, 13 Dec 2022 23:38:31 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.4 https://www.cleverti.com/wp-content/uploads/2020/02/cropped-cleverti_circle-32x32.png Symfony Archives - Cleverti https://www.cleverti.com/tag/symfony/ 32 32 How to make proper form validation with Symfony3 https://www.cleverti.com/blog/php/how-to-make-proper-form-validation-with-symfony3/ Thu, 10 May 2018 18:31:35 +0000 http://2020.cleverti.com/?p=2586 Form validation enables a better user experience while protecting your applications. Our latest hands-on guide shows you how. Collecting user data is a hot topic, with the GDPR adding weight to registration forms. As a Developer, creating forms in your web applications is almost inevitable. When you do it, you should help users to fill [...]

The post How to make proper form validation with Symfony3 appeared first on Cleverti.

]]>
Form validation enables a better user experience while protecting your applications. Our latest hands-on guide shows you how.

Collecting user data is a hot topic, with the GDPR adding weight to registration forms. As a Developer, creating forms in your web applications is almost inevitable. When you do it, you should help users to fill the form correctly without leading them to despair. But you also should make sure that data is filled in a correct format to be processed without damaging your applications.

Your web application must be able to check whether or not data entered in forms is correct. If it is, data can be submitted and stored in a database. Otherwise, the user should get an error message explaining what went wrong. In the following lines, we’ll show you how to get there.

Ok, ready? Let’s start!

In this tutorial, we used Symfony 3.4.8, and two third-party bundles, EWZRecaptchaBundle and blackknight467’s StarRatingBundle.

The first thing you need to do is set a Simfony3 environment from the procedures in the online documentation, and install the two bundles used:

composer require excelwebzone/recaptcha-bundle
composer require blackknight467/star-rating-bundle

The usage and setup procedures for both bundles are described here EWZRecaptchaBundle and here StarRatingBundle.

What we want to achieve, is to simulate a generic product review creation mechanism, by receiving the data sent by the reviewer, validate the data, store it on the database, and display the stored reviews. For this purpose, we will need two endpoints, one for the review creation, and another one for listing them.

So, basically, this is the skeleton of our controller, which we have called ReviewController:

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class ReviewController extends Controller
{
  
   /**
    * @Route("/", name="review")
    */
   public function reviewAction(Request $request)
   {
   }
  
   /**
    * @Route("/list", name="list")
    */
   public function listAction(Request $request)
   {
   }
}

 

Now, we need a data model class, which will receive the data and help on the validation of the form.

Our ReviewModel will have constraints for the validation, and some example properties: name, email, satisfaction, quality, review, and recaptcha.

This recaptcha property will use the validator introduced by the EZWRecaptchaBundle.

And our form will look like this:

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Model\ReviewModel;
use blackknight467\StarRatingBundle\Form\RatingType;
use EWZ\Bundle\RecaptchaBundle\Form\Type\EWZRecaptchaType;

class ReviewType extends AbstractType
{
   /**
    * {@inheritdoc}
    */
   public function buildForm(FormBuilderInterface $builder, array $options)
   {
       $builder
               ->add('name', TextType::class, ['required' => true])
               ->add('email', EmailType::class, ['required' => false])
               ->add('satisfaction', RatingType::class, ['required' => true])
               ->add('quality', RatingType::class, ['required' => true])
               ->add('review', TextareaType::class, ['required' => true])
               ->add('recaptcha', EWZRecaptchaType::class, ['required' => true])
               ->add('submit', SubmitType::class)
               ;
   }
   /**
    * {@inheritdoc}
    */
   public function configureOptions(OptionsResolver $resolver)
   {
       $resolver->setDefaults(array(
           'data_class' => ReviewModel::class,
           'csrf_protection' => false,
           'allow_extra_fields' => true,
       ));
   }

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

 

Next, we need some logic in our reviewAction, which will create the form using our ReviewType class, and populate the ReviewModel.

   /**
    * @Route("/", name="review")
    */
   public function reviewAction(Request $request)
   {
       $form = $this->createForm(ReviewType::class);
       $postedData = null;
      
       if($request->getMethod() == "POST")
       {
           $postedData = new ReviewModel();
           $form = $this->createForm(ReviewType::class, $postedData);
           $form->handleRequest($request);

           if ($form->isSubmitted() && $form->isValid()) {
               // Store the review in the database
               $this->reviewService->createReview($postedData);
               // Set success message
               $this->addFlash('success', 'Your review has been delivered successfuly.');
               // Clear the form, since the data was submitted correctly
               $form = $this->createForm(ReviewType::class);
           }else{
               $postedData = null;
               // Set error message
               $this->addFlash('error', 'There are errors with your submission. Please solve the errors, and try again.');
           }
       }
      
       return $this->render('review/index.html.twig', [
           'form' => $form->createView()
       ]);
   }

 

If the data in the form is valid, we will call a service (ReviewService) to store the posted information. This service was created to handle the creation and listing of our reviews and relates to the Review entity and the ReviewRepository.

Also, the controller will use addFlash() to define the success and error message, which will be used in our twig:

{% for label, messages in app.flashes(['error', 'success']) %}
       {% for message in messages %}
       <div class="confirmation-container">
           {% if label == "success" %}
           <span class="confirmation">
               <span class="fa fa-check-circle-o"></span>
           {% else %}
           <span class="error_confirmation">
               <span class="fa fa-times-circle"></span>
           {% endif %}
               <span>{{ message }}</span>
           </span>
       </div>
       {% endfor %}
   {% endfor %}

 

And finally, the controller passes the form into the twig, which is used for the creation of the form itself, display error messages related to data validation, and keep the form populated with the data if any validation problem is found.

We could generate the whole form automatically, but in this example, we placed each form element, label and error placeholder by hand, in specific positions.

{{ form_start(form) }}
   <div class="form_field">
       {{ form_label(form.name, 'Name', {'label_attr': {'class': 'label'} }) }}
       {{ form_errors(form.name) }}
       {{ form_widget(form.name, {'attr': {'maxlength': 150}}) }}
   </div>
   <div class="form_field">
       {{ form_label(form.email, 'Email address', {'label_attr': {'class': 'label'} }) }}
       {{ form_errors(form.email) }}
       {{ form_widget(form.email, {'attr': {'placeholder': '(optional)', 'maxlength': 254}}) }}
   </div>
   <div class="form_field">
       {{ form_label(form.satisfaction, 'Satisfaction', {'label_attr': {'class': 'label'} }) }}
       {{ form_errors(form.satisfaction) }}
       {{ form_widget(form.satisfaction) }}
   </div>
   <div class="form_field">
       {{ form_label(form.quality, 'Product quality', {'label_attr': {'class': 'label'} }) }}
       {{ form_errors(form.quality) }}
       {{ form_widget(form.quality) }}
   </div>
   <div class="form_field">
       {{ form_label(form.review, 'Your review', {'label_attr': {'class': 'label'} }) }}
       {{ form_errors(form.review) }}
       {{ form_widget(form.review, {'attr': {'maxlength': 1000}}) }}
   </div>
   <div class="form_field_recaptcha">
       {{ form_errors(form.recaptcha) }}
       {{ form_widget(form.recaptcha) }}
   </div>
   <div class="form_field_submit">
       {{ form_widget(form.submit) }}
   </div>
   {{ form_end(form) }}

 

In this project, we already have a CSS formatted layout, and the rendered form will look like this:

If there are any data validation problems, we will get the following:

And in the event of success, we will get the following:

So, we are sure that our form is working correctly, and the validation is being done, including the recaptcha verification.

And now let’s take care of the review listing part. For that we will use the following action:

   /**
    * @Route("/list", name="list")
    */
   public function listAction(Request $request)
   {
       $reviews = $this->reviewService->listReviews();
      
       return $this->render('review/list.html.twig', [
           'reviews' => $reviews
       ]);
   }

 

It will basically just get the list of reviews existing in the database, and pass them into the twig, which will populate a table with that information, as follows:

See? I told you it was simple :)

And that’s all. For further reference, make sure to check the full project in GitHub.  https://github.com/ricardocorreia/symfony3_forms_tutorial

 

Written by Ricardo Correia | Developer at Cleverti

The post How to make proper form validation with Symfony3 appeared first on Cleverti.

]]>
So you do Symfony but want to do Laravel https://www.cleverti.com/blog/php/so-you-do-symfony-but-want-to-do-laravel/ Wed, 25 Apr 2018 18:31:28 +0000 http://2020.cleverti.com/?p=2588 It's widely known Laravel was built using Symfony so a lot of people say it's really easy to make the jump. But then again a lot of people believe in Santa… Never mind! In any case, I decided to give it a try and bought the really nice (AKA cheap) Build Wordpress CMS Clone with Laravel [...]

The post So you do Symfony but want to do Laravel appeared first on Cleverti.

]]>
It’s widely known Laravel was built using Symfony so a lot of people say it’s really easy to make the jump. But then again a lot of people believe in Santa…

Never mind! In any case, I decided to give it a try and bought the really nice (AKA cheap) Build WordPress CMS Clone with Laravel 5 tutorial from a very well-known website.

The first thing you need to know about Laravel (when you come from Symfony) is that Laravel was embedded in a lot of voodoo, witchcraft, black magic and other very mysterious occult things. Which is not necessarily bad, on the contrary. Summing the right spell, it’s really fast to do stuff.

My first controller

The first thing you need to know is that Laravel uses the Blade template engine, which is vaguely similar to Symfony’s Twig. So let’s create our first template (named do.blade.php) in the resources/views/ folder.

<b>My thing is</b>: {{ $myThing }}

 

The second thing you need to know is that Laravel won’t use bundles (in the same way Symfony started to do from version 4). So, let’s create a controller with the artisan make:controller DoSomethingController command, and add the DoSomethingController::do() method as shown below:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class DoSomethingController extends Controller
{
    public function do()
    {
        return view('do', ['myThing' => 'Laravel']);
    }
}

 

And now, let’s define a route for the method in the routes/web.php file.

<?php
Route::get(
    '/do',
    ['uses' => 'DoSomethingController@do', 'as' => 'do_something_do']
);

 

Now, just open the route in your development url (AKA http::/localhost/do) and you will see the lovely output:

My thing is: Laravel

Black Magic

If you know one or two things about PHP, you won’t remember to have seen the view function anywhere in the PHP Language Reference manual… but don’t pull your hair off yet. Check the Laravel helpers reference first.

Laravel includes a variety of global “helper” PHP functions.

Many of these functions are used by the framework itself; however, you are free to use them in your own applications if you find them convenient.

That’s right. There are more functions in Laravel and PHP, Horatio, than are dreamt of in your philosophy.

Is it a bird? Is it a plane? No! It’s a facade!!!

And you will also notice that there is no use clause to import any Route namespace, yet you are using it.

Somebody may think that Laravel is Namespace aware (whatever that means) but it’s not. This is just a facade, (The C reads as SS) what the dictionary defines as a “deceptive outward appearance” and what, in truth, it is.

Facades provide a “static” interface to classes that are available in the application’s service container. Laravel ships with many facades which provide access to almost all of Laravel’s features. Laravel facades serve as “static proxies” to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.

All of Laravel’s facades are defined in the Illuminate\Support\Facades namespace.

And you will use facades a lot if you expect to work with Laravel.

To Infinity and Beyond

There is much more to say about starting with Laravel, but this is just an article, not a book. But fear not young Padawan, soon enough there will be other articles about weird stuff in the Laravel universe.

And may the Force be with you.

 

Written by Eduardo Fernandes | Developer at Cleverti

The post So you do Symfony but want to do Laravel appeared first on Cleverti.

]]>
Symfony: dependency injection ≠ container aware https://www.cleverti.com/blog/php/symfony-dependency-injection-container-aware/ Tue, 27 Feb 2018 18:31:17 +0000 http://2020.cleverti.com/?p=2594 Do you want to know why you shouldn’t inject the Symfony container in your services and learn a bit more about dependency injection? Don’t miss this piece. My mama said never to inject Symfony containers... --- But mom, all Symfony tutorials around say I should use containers… --- And also that if you are a [...]

The post Symfony: dependency injection ≠ container aware appeared first on Cleverti.

]]>
Do you want to know why you shouldn’t inject the Symfony container in your services and learn a bit more about dependency injection? Don’t miss this piece.

My mama said never to inject Symfony containers…

— But mom, all Symfony tutorials around say I should use containers…

— And also that if you are a good kid, you will get a new PS4 from Santa Claus. Do not believe everything they say!

It’s true that most tutorials and Stack Overflow (please, do not try to convince me that you do not use Stack Overflow) answers use containers to get services. However, that doesn’t mean using it is the right way to have access to services.

— So why do they all say I should use containers?

Maybe they all say you should use services, and the simplest way to show how to use a service in a tutorial is with $container->get(‘your_service’). But most tutorials are about how to use a service and they assume you already know what dependency injection is.

You do know what dependency injection is, right?

Don’t worry, most people don’t know as well. Dependency injection is like love. Most people know exactly what love is until somebody actually asks them what it is.

Wikipedia says that

“Dependency injection is a software design pattern that allows the removal of hard-coded dependencies and makes it possible to change them, whether at run-time or compile-time.”

And you say

— WHAT!?!?!?

Yeah… Ok… Dependency Injection is a design pattern in which you use constructors and setters to inject parameters (often called services) that they depend on. The container is the glue that allows the wiring of the service dependencies.

— Errrr… I…

Ok, using a PHP, the right way example. Normally you would have:

<?php

namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

 

But using dependency injection, you have:

<?php

namespace Database;

class Database
{
    protected $adapter;

    public function __construct(MySqlAdapter $adapter)
    {
        $this->adapter = $adapter;
    }
}

class MysqlAdapter {}

 

And the autoloader — what almost every framework has — will use the container to wire the MySqlAdapter service to the Database class.

— Ah, now I got it. But why can’t I just inject the container as the tutorials explain?

<?php

class Database
{
    protected $adapter;

    public function __construct(Container $container)
    {
        $this->container = $container;
        $this->adapter = $this->container->get('MysqlAdapter'):
    }
}

 

Ok, let me answer that. You shouldn’t pass the container because:

Container aware is not dependency injection

This may seem elementary, but the design pattern is dependency injection, not container injection.

Container aware makes the code hard to maintain

Let’s say you have 10 classes dependent on the MegaService and you just find out about the NewMegaService, which does the same thing as MegaService but much better.

If you use dependency injection, you can easily change your services config file to change the definition to inject MegaService instead NewMegaService. If you use container, you will have to change the hardcoded service in every class.

Your IDE likes it

The type hinting of the injected services will allow your IDE to know the type of code its dealing with, and help you with the programming boring tasks like finding out about constants and methods the service has.

For more information, it’s totally worth reading Dan Blow’s answer to In Symfony2, why is it a bad idea to inject the service container, rather than individual services?.

Your class should use only what it needs

It’s a good design practice to make your class use only what it needs. It doesn’t need a container, it just needs a service from the container so, give it what it needs.

Just as a notice, it’s not a bad practice to use setters to inject services instead of constructors. I’m saying this because I heard somebody saying that setters were just a workaround. They’re not!

Especially when you need a lot of services, it may make it easier to read the code to have getters and setters instead of having a constructor with lots of parameters. As everything in life, sometimes common sense is the best design pattern.

 

Written by Eduardo Fernandes | Developer at Cleverti

The post Symfony: dependency injection ≠ container aware appeared first on Cleverti.

]]>
How to setup Docker for a Symfony project https://www.cleverti.com/blog/php/how-to-setup-docker-for-a-symfony-project/ Tue, 05 Dec 2017 13:13:37 +0000 http://2020.cleverti.com/?p=2606 Docker is a fast grower in open source software development. Follow our simple steps to easily set up a Docker environment in your project. What exactly is Docker? Let's start at the beginning and explain a bit what a docker is. In simple words, a docker is a standardised unit of software that is independent [...]

The post How to setup Docker for a Symfony project appeared first on Cleverti.

]]>
Docker is a fast grower in open source software development. Follow our simple steps to easily set up a Docker environment in your project.

What exactly is Docker?

Let’s start at the beginning and explain a bit what a docker is. In simple words, a docker is a standardised unit of software that is independent of its host. The Docker open platform makes it easier for developers to create, deploy and run applications within containers instead of virtual machines, adding a layer of abstraction. A container is an abstraction at the application layer that packages code and dependencies together, therefore eliminating the overhead of a full copy of an operating system.

Before getting started to build our environment, you need to install Docker and Docker image.

The structure

Let’s define the structure of our Docker environment:

We have 4 folders and the first one – build – is responsible for storing the information about each of the dockers that will live in our container. The second one – logs – will be used to store the log file from NGINX and PHP. The mysql folder will store the database in a persistent way in the host and finally the symfony folder will host the Symfony installation.

Inside the build folder, let’s start by creating two folders, one for NGINX and another PHP:

 

NGINX docker

Inside NGINX folder, let’s create a file called Dockerfile, which will host the necessary packages:

FROM debian:stretch
RUN apt-get update && apt-get install -y nginx vim nano

COPY nginx.conf /etc/nginx/nginx.conf
ADD sites-enabled /etc/nginx/sites-enabled
RUN rm /etc/nginx/sites-enabled/default

WORKDIR /var/www/html/symfony

EXPOSE 80
EXPOSE 443

CMD ["nginx"]

 

Explaining a bit, each of the base commands declared inside our Docker file:

  • FROM is to define in which image this docker will be based on, in our case I’ll use the latest build from Ubuntu,
  • RUN is used to execute command inside the docker,
  • COPY will copy a specific file from the host to the docker,
  • ADD will copy a specific folder from the host to the docker,
  • WORKDIR is used to define the base folder inside the docker,
  • EXPOSE defines the ports that will be available to connect from the host to the guest,
  • CMD is responsible for executing a command/command.

In this docker file, we’ll install NGINX packages on Ubuntu images and will copy the nginx.conf file from the host and add the content of the folder sites-enabled and finally execute NGINX.

Inside the nginx.conf file we have the following:

user www-data;
pid /run/nginx.pid;

worker_processes  1;
daemon off;

events {
        worker_connections 768;
        # multi_accept on;
}

http {
        client_max_body_size 20M;
        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

Typically this is the base of nginx.conf file, with the exception of running on a single process, due to worker_process and running on the foreground with daemon off;

Inside the sites-enabled folder, we’ll have a unique file, called default_symfony with the following:

server {
    server_name _;
    root /var/www/html/symfony/web;

    location / {
        # try to serve file directly, fallback to app.php
        try_files $uri /app_dev.php$is_args$args;
    }
    # DEV
    # This rule should only be placed on your development environment
    # In production, don't include this and don't deploy app_dev.php or config.php
    location ~ ^/(app_dev|config)\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }
    # PROD
    location ~ ^/app\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        # Prevents URIs that include the front controller. This will 404:
        # http://domain.tld/app.php/some-path
        # Remove the internal directive to allow URIs like this
        internal;
    }

    # return 404 for all other php files not matching the front controller
    # this prevents access to other php files you don't want to be accessible.
    location ~ \.php$ {
        return 404;
    }

    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
}

This file is the default Symfony configuration for NGINX, with the exception of fastcgi_pass php:9000. This will tell NGINX to use the PHP from the host PHP.

PHP docker

Inside PHP folder, let’s create a file called Dockerfile, which will host the necessary packages:

FROM ubuntu:17.10

RUN apt-get update && apt-get install -my \
  curl \
  wget \
  php-curl \
  php-fpm \
  php-gd \
  php-xsl \
  php-mysqlnd \
  php-mcrypt \
  php-cli \
  php-intl \
  php-bz2 \
  php-zip \
  php-mbstring \
  git \
  zip \
  php-apcu \
  php-redis \
  php-opcache

RUN mkdir /run/php

ADD conf/www.conf /etc/php/7.1/fpm/pool.d/www.conf
ADD conf/php-fpm.conf /etc/php/7.1/fpm/php-fpm.conf

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer
RUN chmod +x /usr/local/bin/composer
RUN ln -snf /usr/share/zoneinfo/Europe/Lisbon /etc/localtime

WORKDIR /var/www/html/symfony

EXPOSE 9000

CMD ["php-fpm7.1"]

In this docker file, we’ll install PHP packages on Ubuntu image with composer and will copy the www.conf and php-fpm.conf files from the host and add the content of the guest folders.

Inside www.conf add

[www]
user = www-data
group = www-data
listen = 0.0.0.0:9000
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

And php-fpm.conf file

[global]
pid = /run/php/php7.1-fpm.pid
error_log = /var/log/php7.1-fpm.log
daemonize = no
include=/etc/php/7.1/fpm/pool.d/*.conf

And the base configuration for this Dockerfile, that will host PHP is done.

Docker-compose (The Connector)

Finally, the docker-compose.yml file will be responsible for connecting all the dockers with a local network, inside that file we’ll have:

version: '3'
services:
  php:
    build: build/php
    expose:
      - '9000'
    depends_on:
      - db
    volumes:
      - ./:/var/www/html/symfony:cached
      - ./logs:/var/log
  web:
    build: build/nginx
    restart: always
    ports:
        - ‘81:80'
    depends_on:
        - php
        - db
    volumes:
        - ./:/var/www/html/symfony:cached
        - ./logs:/var/log/nginx
  db:
    image: mysql/mysql-server:latest
    environment:
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_ROOT_HOST=${MYSQL_ROOT_HOST}
    ports:
      - "3307:3306"
    volumes:
      - ./mysql:/var/lib/mysql

We’ll have 3 dockers, one for php the will expose the port 9000 to be consumed by nginx and deal with the php files. Another for nginx, called web that will expose the por 81 to the host to be able to access the docker container and the last one db, which will run mysql.

To start the environment you’ll need to run the command:

$ docker-compose build

That will build the containers.

Remember to add your project to the Symfony folder or simply run:

$ docker-compose up

To start the docker network.

Now, all you have to do is open your browser on the address http://localhost:81.

As you can see, it’s not that hard! I hope this guide is useful and makes your developer days easier.

 

Written by Pedro Resende | Software Development Manager and Team Leader at Cleverti

The post How to setup Docker for a Symfony project appeared first on Cleverti.

]]>
How to set a symfony based rest api with oauth https://www.cleverti.com/blog/php/how-to-set-a-symfony-based-rest-api-with-oauth/ Fri, 10 Nov 2017 13:13:24 +0000 http://2020.cleverti.com/?p=2617 So, you want to set a Symfony based REST API… Allow us to give you a few tips on how to proceed. SensioLabs, the creators of Symfony, describe it as “a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony. It´s only natural you´re eager [...]

The post How to set a symfony based rest api with oauth appeared first on Cleverti.

]]>
So, you want to set a Symfony based REST API… Allow us to give you a few tips on how to proceed.

SensioLabs, the creators of Symfony, describe it as “a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony. It´s only natural you´re eager to get started with it! This tutorial will show you how to set a Symfony based REST API using OAuth as authorization protocol.

 

First things first

You will need to install a Symfony environment. The official documentation guides on this process and explains how to solve common issues.

We used the latest stable version at the time we were writing, which is Symfony 3.3.10. Also, you may want to set an Apache or Nginx VirtualHost, so you can access your app from your web server. The best way to do this is following the official docs.

And try to access your app by calling your host in the browser:

https://puu.sh/ya0jD/4cadb8b9d6.png

So far so good…

Let’s deal with some requirements

Next thing we’ll need is FOSRestBundle to handle the REST requests in our application, and again you should install it as described in the official docs.

$ composer require friendsofsymfony/rest-bundle

And then add the bundle to your app/AppKernel.php:

<?php

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            // ...
            new FOS\RestBundle\FOSRestBundle(),
        ];

        // ...
    }
}

Since we’ll need to deal with content serialization and deserialization, you will also need to use JMSSerializerBundle

 

Start by creating a Bundle

Next thing we’ll do is creating a bundle where we can set our REST controller and our routes, and let’s call it MySuperRestBundle:

$ php bin/console generate:bundle --namespace=cleverti/MySuperRestBundle --no-interaction

Now it’s time for the controller

Now that our bundle is created we will need a controller as well:

$ php bin/console generate:controller --no-interaction --controller=cleverti\MySuperRestBundle:Rest

This will create the controller class in src/cleverti/MySuperRestBundle/Controller/RestController.php, which is where we will focus right now.

The generate:controller command will only generate a base skeleton of your controller class, which means that you will need to add all the mechanics for your controller to respond to the routes you want to define, and this is done by using Actions.

By default, our controller looks like this:

<?php

namespace cleverti\MySuperRestBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class RestController extends Controller
{
}

… which means that we will need to make a few changes. After all, this is just the initial skeleton.

So, let’s start by defining a route for our controller.

You can set a prefix for your controllers, which is pretty useful if you want to have plenty of routes under the same controller, and several controllers under the same bundle. For that you should add the following in app/config/routing.yml:

cleverti_my_super_rest_bundle:
    resource: "@clevertiMySuperRestBundle/Resources/config/routing.yml"
    prefix:   /api

In this case our prefix for our bundle will simply be /api. And then we need to tell Symfony that our controller will be expecting a REST request, so we should add the following in src/cleverti/MySuperRestBundle/Resources/config/routing.yml:

cleverti_my_super_rest_controller:
    type:     rest
    resource: cleverti\MySuperRestBundle\Controller\RestController

Now we are ready to define our routes in our controller. Because we will use REST, our controller will not extend Symfony’s default controller, but FOSRestController instead.

So, let’s define a test action for us to see the API magic at work, and we will call it restGetAction in this case. We will also use an annotation to set our route, like this one: @Get(“/get/cleverti”).

This route will define both your method, and path to call. Since this controller is set to have the prefix /api you will need to call it by:  GET /api/get/cleverti. We should get something like this in our controller:

<?php

namespace cleverti\MySuperRestBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\Get;

class RestController extends FOSRestController
{
    /**
     * Here goes our route
     * @Get("/get/cleverti")
     */
    public function restGetAction(Request $request)
    {
        // Do something with your Request object
        $data = array(
            "name" => "cleverti",
            "extra" => "Is awesome!"
        );
        $view = $this->view($data, 200);
        return $this->handleView($view);
    }
}

And we are almost ready.

We just need to set some basic settings in to make FosRestBundle to listen for REST calls, and since we will use its view engine we won’t need to set any twig template for our responses. So, add the following to your app/config/config.yml:

fos_rest:
    routing_loader:
        default_format: json
    view:
        view_response_listener: true

And now go ahead, and call your route with your favorite REST client, or since the method defined for this route is GET you can also call if from your web browser. We used Insomnia as REST client in our example.

https://puu.sh/ya8kI/c07730b90e.png

And success!

Out API listens to your request, and answers with a JSON response, exactly as it was defined. And now you ask me: What about access control? I don’t want most of my API methods to be publicly available…

Setting up OAuth

There are several authorization methods and one of the most used is OAuth 2.0. It allows you to use authentication with an external provider, which is kind of cool if you intend to use Twitter, Facebook or other provider for your users to identify themselves. In our example we will use FOSUserBundle as user provider, so, let’s create two bundles, one for the FOSUserBundle entity, and another bundle for the FOSOAuthServerBundle entities that we need to set during the installation process:

$ php bin/console generate:bundle --namespace=cleverti/UserBundle --no-interaction
$ php bin/console generate:bundle --namespace=cleverti/OAuthBundle --no-interaction

Then, you should install FOSUserBundle as explained in the official documentation.

And then install FOSOAuthServerBundle as shown here.

Also, don’t forget to make /api available for authenticated users only, by adding it in the access_control block, as follows:

security:
    ...
    access_control:
        - { path: ^/api, roles: [ IS_AUTHENTICATED_FULLY ] }

The path section will be the path of your routes, partial or absolute, that you want to protect. And the roles part is where you define the roles that your users must have, in order to being able to access the routes. Since this is just an example I will not focus on the roles, I am just defining that any authenticated user may have access, but not an anonymous user. You can know more about the roles here. This is how my app/config/config.yml looks like:

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: services.yml }
    - { resource: "@clevertiMySuperRestBundle/Resources/config/services.yml" }
    - { resource: "@clevertiOAuthBundle/Resources/config/services.yml" }
    - { resource: "@clevertiUserBundle/Resources/config/services.yml" }

parameters:
    locale: en

framework:
    #esi: ~
    #translator: { fallbacks: ['%locale%'] }
    secret: '%secret%'
    router:
        resource: '%kernel.project_dir%/app/config/routing.yml'
        strict_requirements: ~
    form: ~
    csrf_protection: ~
    validation: { enable_annotations: true }
    #serializer: { enable_annotations: true }
    templating:
        engines: ['twig']
    default_locale: '%locale%'
    trusted_hosts: ~
    session:
        # https://symfony.com/doc/current/reference/configuration/framework.html#handler-id
        handler_id: session.handler.native_file
        save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
    fragments: ~
    http_method_override: true
    assets: ~
    php_errors:
        log: true

# Twig Configuration
twig:
    debug: '%kernel.debug%'
    strict_variables: '%kernel.debug%'

# Doctrine Configuration
doctrine:
    dbal:
        driver: pdo_mysql
        host: '%database_host%'
        port: '%database_port%'
        dbname: '%database_name%'
        user: '%database_user%'
        password: '%database_password%'
        charset: UTF8
    orm:
        auto_generate_proxy_classes: '%kernel.debug%'
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true

# Swiftmailer Configuration
swiftmailer:
    transport: '%mailer_transport%'
    host: '%mailer_host%'
    username: '%mailer_user%'
    password: '%mailer_password%'
    spool: { type: memory }

# FosRestBundle Configuration
fos_rest:
    routing_loader:
        default_format: json
    view:
        view_response_listener: true

# FOSUserBundle Configuration
fos_user:
    db_driver: orm
    firewall_name: main
    user_class: cleverti\UserBundle\Entity\User
    from_email:
        address: "%mailer_user%"
        sender_name: "%mailer_user%"

# FOSOAuthServerBundle Configuration
fos_oauth_server:
    db_driver: orm
    client_class:        cleverti\OAuthBundle\Entity\Client
    access_token_class:  cleverti\OAuthBundle\Entity\AccessToken
    refresh_token_class: cleverti\OAuthBundle\Entity\RefreshToken
    auth_code_class:     cleverti\OAuthBundle\Entity\AuthCode
    service:
        user_provider: fos_user.user_provider.username
        options:
            access_token_lifetime: 86400
            refresh_token_lifetime: 1209600
            auth_code_lifetime: 30

 

And this is my app/config/security.yml:

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: bcrypt
        
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN
    providers:
        in_memory:
            memory: ~

        fos_userbundle:
            id: fos_user.user_provider.username
            
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
            
        oauth_token:
            pattern:    ^/oauth/v2/token
            security:   false
        oauth_authorize:
            pattern:    ^/oauth/v2/auth
            form_login:
                provider: fos_userbundle
                check_path: /oauth/v2/auth_login_check
                login_path: /oauth/v2/auth_login
                use_referer: true
        api:
            pattern:    ^/api
            fos_oauth:  true
            stateless:  true
            anonymous:  false
        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                csrf_token_generator: security.csrf.token_manager
            
            anonymous: true
    access_control:
        - { path: ^/api, roles: [ IS_AUTHENTICATED_FULLY ] }

From this point on, if you try to make another call to your route you should no longer have access since you are not authenticated yet. This is what you should get:

https://puu.sh/yagcZ/f9353f54b3.png

When we’re done with the installation of FOSUserBundle and FOSOAuthServerBundle we’ll need to create an OAuth client and a User, so we can create Oauth Access Tokens. We’ll need that for our request.

So, create the following command at src/cleverti/OAuthBundle/Command/ClientCreateCommand.php:

<?php
namespace cleverti\OAuthBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ClientCreateCommand extends ContainerAwareCommand
{
    protected function configure ()
    {
        $this
            ->setName('oauth:client:create')
            ->setDescription('Creates a new client')
            ->addOption('redirect-uri', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Sets the redirect uri. Use multiple times to set multiple uris.', null)
            ->addOption('grant-type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Set allowed grant type. Use multiple times to set multiple grant types', null)
        ;
    }
    protected function execute (InputInterface $input, OutputInterface $output)
    {
        $clientManager = $this->getContainer()->get('fos_oauth_server.client_manager.default');
        $client = $clientManager->createClient();
        $client->setRedirectUris($input->getOption('redirect-uri'));
        $client->setAllowedGrantTypes($input->getOption('grant-type'));
        $clientManager->updateClient($client);
        $output->writeln("Added a new client with  public id <info>".$client->getPublicId()."</info> and secret <info>".$client->getSecret()."</info>");
    }
}

And create an OAuth client by running the command you just created with two options – the redirect URIs you desire to use, and the grant types you want to allow this client to use, which should give you an output like the following:

$ php bin/console oauth:client:create --redirect-uri=https://www.cleverti.com --grant-type=password --grant-type=refresh_token
Added a new client with  public id 1_4654cpi0zu0wokk4g04gk0w444wkkwcs4sg0okoo0gks0gcokg and secret 1h7xdjjo6ixwwow0w00oss4sgc0w8o48ocgw808w0gg4s40owc

And you can use the default command provided by FOSUserBundle to create a user:

$ php bin/console fos:user:create ricardo.correia [email protected] password

Now let’s try to request an Access Token, by making a POST request to the OAuth route /oauth/v2/token. This route expects grant_type with value password, the client_id and client_secret, and the username and password of the user:

{
    "grant_type": "password",
    "client_id": "1_4654cpi0zu0wokk4g04gk0w444wkkwcs4sg0okoo0gks0gcokg",
    "client_secret": "1h7xdjjo6ixwwow0w00oss4sgc0w8o48ocgw808w0gg4s40owc",
    "username": "ricardo.correia",
    "password": "password"
}

If we make a request with our REST client, we get the following result:

https://puu.sh/yai0o/cd00e8ee52.png

Ok, everything is going great so far, and we got our access token.

Now we’ll need to use our access token in all our requests, by setting an Authorization header, as follows:

Authorization: Bearer MTI4ZmUyNTg1ZDgwM2Y0ZmJlZjg3OWNlNTA2ZDE5ZTk4ZTQzZGMzNjllOGE4YWI5Yzc0ZWQxMWQ1MjVjNmY5MA

So, let’s try to access our protected route /api/get/cleverti, but this time using the Authorization header:

https://puu.sh/yaii9/6982444163.png

Success! Our route is protected and available to authenticated users only, using OAuth 2.0. And it’s pretty much it.

Also, you may want to refresh the access token when its lifetime expires, and this is done using the /oauth/v2/token route as well, but this time you will need to send the grant_type as refresh_token, client_id and client_secret, and the refresh_token instead of user and password:

{
    "grant_type": "refresh_token",
    "client_id": "1_4654cpi0zu0wokk4g04gk0w444wkkwcs4sg0okoo0gks0gcokg",
    "client_secret": "1h7xdjjo6ixwwow0w00oss4sgc0w8o48ocgw808w0gg4s40owc",
    "refresh_token": "OWQyNTI1MDk4OTlhZTk0NDdlYzA0YTM4ZTFlZDZjMmQxNDY3OTBjMzBiNTYzOWMwMTgxN2UwMGYwYWE3M2RiYw"
}

And that call will return a new access_token and a new refresh_token:

https://puu.sh/yaiTN/94db816ef3.png

And that’s all for now. You’re good to go!

If you need, you can also access the demo Git project here.

We hope you enjoy your coding as much as we do. Have fun!

Written by Ricardo Correia | Developer at Cleverti

The post How to set a symfony based rest api with oauth appeared first on Cleverti.

]]>