Internship 2018

What I learnt during the internship...

Moving data from a CSV document to a database via a web server.

The main objective of this project is to move all the information contained in a CSV file (table containing information from a spreadsheet made by Microsoft Excel, or Libreoffice Calc) to a database via a web server.

This project allowed me to explore symfony 4's infrastructure and to learn how to parse information stored in a CSV document


On the right: CSV document, in the middle: The model that extracts the information to put them on the website (on the right).


All the information from the spreadsheet is to be parsed and put into the database when the user follows the "/csv" route. My first task was to create that route.


1: Creating a route in Symfony 4

Starting with the folder routes.yaml where we determine the methode linked to the controller at "monDomaine.fr/csv"

In the route config file see below, the redirection triggers the csv() method of the "BDD_Controller" controller.


# Location: projetSymfony/config/routes/routes.yaml

csv:

    path: /csv

    controller: App\Controller\BDD_Controller::csv


When we visite the route ending in "/csv" the method should now be called.

Symfony 4 file tree of the project:


2: Le controller, the models...


This is the controller that will create the CSV_Model entity that will parse the information from the and will fill the "$document" object so it can contain all the information present in the document that we can then access it's functions enabling us to get a collection of companies, extracted from the big blob of information that is that of the csv file. (in the variable "$entreprise" in the above screenshot).

From that list we will then be able to itterate through each company as individual objects and save("Persist") them one by one to then save them to the database with the "flush()" function from Doctrine/ORM ($entityManager).

What is an Object Relational Mapper?

Type of computer program that places itself as an interface between an application and relational database to simulate an object oriented database.

This program defines the correspondencies between the schematics of the database and the classes of the application.

We could designate it as, «a layer of abstraction between the applcation and the database».

- Wikipedia


When all of the information has been grouped in the different company objects and persisted and ready to be saved to the database we execute the "flush()" method from doctrine (Entity Manager):

$entityManager->flush()

This action saves all the mapped objects (mapped with the "persist() method") to the database


3: The CSV Model (Parsing a CSV file)

The main goal of the CSV_Model object is to read the CSV document, parse the information on each line and create an ORM object with the corresponding information and return the created entities.

In the case where we want it to return a list of companies through the function "getEntreprises()".

The first task we want the program to di is to find the csv file that we want to parse, to find the file we will be using the "finder" component of symfony that we will place at the top of the class:

use Symfony\Component\Finder\Finder;

To install Finder with composer:

composer require symfony/finder

We retrieve all the documents with the name "entreprises.csv":

$finder = new Finder();

$finder->files()->in('../')->name('entreprises.csv');

This will return an object called $finder a repository of all files found in which we will retrieve our csv file that we will assign to the object: $csv That will contain all the contents of the file: "entreprises.csv".

We now need to transform this object into a collection of objects (the different companies) each with only the properties that we need.

I created a classe "entreprise" containing all the properties needed and excluding the ones that weren't needed with "make:entity" in the creation of the classe (template where each instance will be a different company).

php bin/console make:entity

While parsing the document I realized that the first line (row) contains all the heads of the columns of the spreadsheet, and all the other rows where arrays that contained the information in the same order as the collumns.

So I created an array of the column titles so that I knew for example, that the second entry of line 4 would correspond to the second column

In a loop, I associate each property to the head of each column, for example, column 1 would be the name of the company, so I associate the information in this box with the name of the company of the corresponding line.

For every row, once the object (a company) is filled with information extracted from the CSV, I add it to the repository that will be returned if we call the function "getEntreprises ()"

The loop ends when all rows have been processed and the repository of "company" objects is full and ready to be registered by entityManager in the database.


Here is what the CSV model looks like::

The properties of the clss:

The method getCSV:


This project, all while being well above my current level of competence, has given me the opportunity to learn and familiarise myself with the infrastructure of symfony4, the functionalities of Doctrine and composer. I really liked this project as well as being able to manage the transfer of data between the CSV file and the database through an object generated by doctrine ORM.


Changing the property of an entry field in TWIG

The objective here is to change the search bar of EasyAdmin to a dropdown menu to search by entity


I will be using in this Symfony4 project the functionalities of Doctrine and the template engine TWIG.

I have decided to do it in a few seperate steps:


1: In the controller, I'll be overriding the function "listAction()"

located in src\..\AdminController::listAction() I'll be adding a parameter containing a list of all the sales people ($commercials) that will then be passed to TWIG for a drop down menu.


- Creating a repository containing all the users (as entities):

$commercials = $this->getDoctrine()->getRepository('App\Entity\User')->findAll();

- Filtering this repository in order to only have a list of sales people.

$commercialList=[];

foreach ($commercials as $user){

     if($user->hasRole('ROLE_COMMERCIAL')){

         array_push($commercialList,$user);

     }

}

- This list is placed in a parameter that can then be used by the template engine:

$parameters["commercials"] = $commercialList;
2: Fetching the list provided as a parameter and making the dropdown.

- In the block: "search_form" I override the search field:

{# Location: src/templates/easy_admin/list.html.twig #}

{% block search_form %}

{# ... #}

{% endblock %}

3: The dropdown is now completed, all that is left to do is add functionality. ¯\_(ツ)_/¯

Change displayed content according to the users role

Here is a project that has the limit some users to see some features, allowed me to familiarize myself with TWIG.

A template engine allowing for:

    - The seperation between the presentation of data and it's processing
    - Web page personalisation
    - Easier maintenance and modularity

Usually the templates are stored in "/vendor/easycorp/easyadmin-bundle/src/Resources/views/", to overide them, we recreate the same file tree starting from "easy_admin" in the templates folder and under the name of the folder I wish to replace

For example the folder: "src/templates/easy_admin/list.html.twig"

We can see that two parts of this folder are responsible for the edit and delete.

Edit Delete Actions 1: Show the different views according to the users role with TWIG and EasyAdminBundle:

I assigned a boolean value (true or false) to a variable "seeActions" which will determin whether the user will be able to see the "edit/delete" buttons

{% if seeActions is same as ("true") %}

  {% if _list_item_actions|length > 0 %}

    /* EasyAdminBundle code that displays the column title "Actions" */

    {% endif %}

{% endif %}

    /* [ ... ] */

{% if seeActions is same as ("true") %}

  {% if _list_item_actions|length > 0 %}

    /* EasyAdminBundle code that displays "Edit" and "Delete" buttons */

  {% endif %}

{% endif %}

The value of the "seeActions" variable, see above, is determined by the users role:

{# For the admin' #}

{% set seeActions = "false" %}

{% if is_granted(['ROLE_ADMIN']) %}

    {% set seeActions = "true" %}

{# for the production manager #}

{% elseif is_granted(['ROLE_MANAGER_PROD']) %}

    {% set seeActions = "true" %}

{% endif %}

2: The visual seperation depending on the role with TWIG and EasyAdminBundle:

On the left, we can see the login page that redirects the user towards others that lists their meetings.

We can also the TWIG template that generates the page

Only the production manager can modify or delete meetings.

Vues selon role de twig

Limiting actions to specific roles from inside the EasyAdminBundle config: "easy_admin.yml"

In order to limit certain actions, I am using the configuration of EasyAdminBundle: "config/packages/easy_admin.yaml"

Edit Delete Actions Controller

In the above image, the "edit" function for the Rdv entity needs the "ROLE_ADMIN" ou "ROLE_MANAGER_PROD" roles,the telemarketer now cannot modify this entity.

Edit Delete Actions Controller

Doctrine Object Relational Mapper

The documentation for the doctrine ORM being very complete has helped me learn how an ORM works pretty quickly.

The "User" class just below contains two properties that have helped me apply the given examples given in the documentation

// src/Entity/User.php

namespace App\Entity;


use FOS\UserBundle\Model\User as BaseUser;

use Doctrine\ORM\Mapping as ORM;

use Doctrine\Common\Collections\ArrayCollection;


/**

* @ORM\Entity

* @ORM\Table(name="fos_user")

*/

class User extends BaseUser

{

    /**

     * @ORM\Id

     * @ORM\Column(type="integer")

     * @ORM\GeneratedValue(strategy="AUTO")

     */

    protected $id;


     /**

     * @ORM\OneToOne(targetEntity="User", fetch="EAGER")

     * @ORM\JoinColumn(name="linkedComm", referencedColumnName="id")

     */

    private $linkedComm;


    /**

     * @ORM\OneToMany(targetEntity="Rdv", mappedBy="Commercial")

     */

    private $rdvs;


    public function __construct()

    {

        parent::__construct();

        $this->roles = ['ROLE_USER'];

        $this->enabled = true;

        $this->rdvs = new ArrayCollection();

    }


EAGER/LAZY Loading...

In ths project each telemarketer had to be linked to its relative marketing expert.

In the event of the creation of a new meeting by the telemarketer, his associated marketing expert needs to automatically be associated to him in the database

In the creation of a form I came across a problem where all the properties of the linked marketing expert where empty to the exception of his ID:

objet associé vide

In the database, I noticed that they are evidently linked but when trying to access them, the properties where coming up as empty.

objet associé vide

After some research the source of the problem seemed to be from the way I was fetching the data, and had to change it to fetch it in and "EAGER" method, a method that loads the whole object with all it's properties ' fetch="EAGER" ' when making the association between the telemarketer and the marketing expert. (See "$linkedComm" below)

solution objet associé vide

The telemarketer and his associated expert:

solution objet associé vide