Introduction à Symfony2
Upcoming SlideShare
Loading in...5
×
 

Introduction à Symfony2

on

  • 8,182 views

Cette nouvelle version du framework a été entièrement réécrite afin de tirer profit de PHP 5.3 d'une part mais également de corriger les erreurs du passé avec symfony 1.x....

Cette nouvelle version du framework a été entièrement réécrite afin de tirer profit de PHP 5.3 d'une part mais également de corriger les erreurs du passé avec symfony 1.x.

Cette nouvelle version regorge de fonctionnalités puissantes pour vous aider à bâtir des applications web maintenables, pérennes, performantes et évolutives.

Cette présentation donne un aperçu des nouvelles fonctionnalités de Symfony2 comme l'architecture MVC, les tests automatisés ou bien encore l'envoi d'emails.

Statistics

Views

Total Views
8,182
Views on SlideShare
8,175
Embed Views
7

Actions

Likes
3
Downloads
224
Comments
0

2 Embeds 7

http://www.sfexception.com 4
http://www.mefeedia.com 3

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Introduction à Symfony2 Introduction à Symfony2 Presentation Transcript

  • Introduction à Symfony2
  • •  Hugo HAMON (@hhamon) •  Responsable des formations Sensio Labs •  Secrétaire Général de l’AFUP •  10 ans de développement web dont 8 avec PHP •  Coauteur d’ouvrages Eyrolles •  Apprendre-PHP.com / HugoHamon.com
  • Qu’est-ce que Symfony2 ?
  • Un framework web
  • PHP 5.3
  • Objectifs ?
  • •  Développer plus vite et mieux •  Faciliter le travail en équipe •  Pérenniser les applications •  Simpli er la maintenance et les évolutions •  Se concentrer sur la logique métier •  Ne pas réinventer la roue !
  • Symfony2 intègre les meilleurs outils Open-Source PHP
  • Symfony Components Dependency Injection Container Request Handler Event Dispatcher Console YAML …
  • Zend Framework PHPUnit Doctrine2 Swift Mailer Twig
  • Différences avec symfony 1.x ?
  • Même philosophie, Même outillage, Moins de concepts, Plus de exibilité Performances accrues 
  • Où en est-on aujourd’hui ?
  • •  Version ALPHA •  Briques logicielles manquantes •  Documentation incomplète •  L’API peut encore beaucoup évoluer •  Version stable repoussée à début Mars 2011
  • Je veux tester Symfony2 ! git clone http://github.com/symfony/symfony-sandbox.git http://www.symfony-reloaded.org
  • Je veux développer un projet client maintenant avec Symfony2 ? A ta place, je ne ferai pas ça…
  • Quel outillage ?
  • •  Sécurité •  Extensibilité •  Architecture MVC •  I18N & L10N •  URLs élégantes •  Authenti cation et ACLs •  DBAL & ORM •  Tests unitaires •  Outils de débogage •  Tests fonctionnels •  Formulaires •  Cache •  Con guration •  Admin Generator
  • Architecture d’un projet Symfony2
  • Un Projet Symfony2 est un répertoire qui se compose d’une Application, d’un jeu de Bundles et de librairies.
  • . |-- LICENSE |-- README |-- app/ | |-- AppCache.php | |-- AppKernel.php Répertoire  de  l’Applica0on   | |-- cache/ | |-- config/ | |-- console | |-- logs/ | |-- phpunit.xml.dist | `-- views/ |-- bin/ | |-- create_sandbox.sh | |-- install_vendors.sh | |-- prepare_vendors.sh | `-- update_vendors.sh |-- src/ | |-- Application/ | |-- Bundle/ Code  de  l’Applica0on  +     | |-- autoload.php Bundles  +     | `-- vendor/ Librairies  externes   `-- web/ |-- bundles/ |-- check.php |-- index.php Dossier  public   `-- index_dev.php
  • Une Application est un répertoire qui contient la con guration pour un jeu de Bundles donné.
  • Structure d’une application app/ |-- AppCache.php |-- AppKernel.php The  AppKernel  class  is  the   |-- cache/  main  class  of  the  applica0on   |-- config/ | |-- config.yml | |-- config_dev.yml | |-- config_test.yml | |-- routing.yml Configura0on  files   | `-- routing_dev.yml |-- console |-- logs/ | `-- dev.log |-- phpunit.xml.dist Logs  and  applica0on  templates   `-- views/ |-- layout.php
  • Un Bundle est un ensemble structuré et cohérent de chiers qui implémentent une fonctionnalité (un blog, un forum, …) et qui peut facilement être partagé avec d’autres développeurs.
  • symfony 1.x => Plugins Symfony 2.x => Bundles
  • + BlogBundle/ Code  source  du  bundle  :  contrôleurs   |-- Controller/ | `-- BlogController.php modèles,  formulaires…   |-- Entity/ | `-- Post.php |-- Form/ | `-- PostForm.php Le  fichier  BlogBundle.php  est   |-- BlogBundle.php |-- Model/ obligatoire  et  con0ent  la  classe  qui   | `-- PostRepository.php déclare  le  bundle.   |-- Resources/ | |-- config/ | | `-- routing.yml | |-- views/ | | `-- Blog/ Un  bundle  peut  contenir  de  la   | | |-- showPost.php configura0on,  des  templates  et  des   | | `-- listPost.php ressources  web.   | `-- web/ | `-- css/ | `-- blog.css `-- Tests/ Un  bundle  peut  aussi  contenir   `-- Controller/ `-- BlogControllerTest.php des  scripts  de  tests  PHPUnit.  
  • Sécurité
  • XSS CSRF SQL Injections
  • Le dossier web/ du projet est le seul accessible depuis un navigateur web
  • Routage et URLs
  • Le système de routage a pour rôle de convertir une URL en une réponse web.
  • Elles sont propres et élégantes a n d’exposer des informations pertinentes et de masquer l’implémentation technique… http://www.domain.com/blog/2010/09/15/symfony2-rocks
  • Con guration des URLs en YAML # src/Bundle/BlogBundle/Resources/config/routing.yml post_details: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Blog:showPost } Exemple d’URL générée http://www.domain.com/blog/2010/09/15/symfony2-rocks
  • Association URL et Code ?
  • # src/Application/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } }
  • # src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  du  Bundle   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller Un  dossier  /  namespace   { public function indexAction($name) { // ... } }
  • # src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  du  contrôleur   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller Une  classe   { public function indexAction($name) { // ... } }
  • # src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  de  l’ac0on   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } Une  méthode   }
  • # src/Application/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } }
  • Les paramètres peuvent être passés dans un ordre arbitraire post_details: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Blog:showPost } namespace ApplicationBlogBundleController; class BlogController extends Controller { public function showPostAction($slug, $year) { // ... } }
  • Architecture MVC
  • •  Séparation du code en trois couches – Logique métier dans le Modèle – Logique applicative dans le Contrôleur – Affichage dans la Vue (templates) •  Modularité et découplage du code •  Maintenance simpli ée sur le code source •  Code testable unitairement et plus robuste
  • Les actions pour la logique applicative. Elles se situent dans les Contrôleurs.
  • ๏  Une action est accessible depuis une URL # src/Application/BlogBundle/Resources/config/routing.yml post_show: pattern: /blog/article/:id/show defaults: { _controller: BlogBundle:Blog:show }
  • # src/Application/BlogBundle/Controller/BlogController.php namespace ApplicationBlogBundleController; use SymfonyBundleFrameworkBundleControllerController; class BlogController extends Controller { public function showAction($id) Paramètres  de  l’url   { // find the article by its id $post = ...; // render the view return $this->render('BlogBundle:Blog:show', array('post' => $post)); } } Template  à  rendre   Variables  du  template  
  • Les templates constituent la couche de présentation des données, la vue.
  • ๏  Syntaxe alternative de PHP ๏  Quelques brèves instructions PHP (echo, if, foreach…) ๏  Echappement automatique des variables # src/Application/BlogBundle/Resources/views/Blog/show.php <?php $view->extend('::layout') ?> Layout  de  décora0on   <h2><?php echo $post->getTitle() ?></h2> <p> <?php echo $post->getContent() ?> </p> Variables  échappées  =>  pas  de  XSS  !!!  
  • Héritage de Vues
  • # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view->extend('::layout') ?> Hello <?php echo $name ?>! étend # app/views/layout.php <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
  • Héritage de Vues layout.php _content Hello Hugo! index.php
  • # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view->extend('HelloBundle::layout') ?> Hello <?php echo $name ?>! # src/Application/HelloBundle/Resources/views/layout.php <?php $view->extend('::layout') ?> <h1>Hello Application</h1> <div> <?php $view['slots']->output('_content') ?> </div> # app/views/layout.php <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
  • Héritage multiple ::layout.php HelloBundle::layout.php _content Hello Hugo! _content index.php
  • Les Slots sont des fragments dé nis dans un template et affichés dans un layout décorant ce dernier.
  • # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view['slots']->set('title', 'Hello World app') ?> # app/views/layout.php <html> <head> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
  • Symfony fournit des mécanismes simples pour évaluer et inclure des templates dans un autre # src/Application/HelloBundle/Resources/views/Hello/hello.php Hello <?php echo $name ?>! # Including another template in the current template <?php echo $view->render('HelloBundle:Hello:hello', array('name' => $name)) ?> src/Bundle/HelloBundle/Resources/views/Hello/hello.php  
  • Symfony offre également un moyen d’inclure le rendu d’une action depuis une vue… # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view['actions']->output('HelloBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green’ )) ?>
  • # src/Application/HelloBundle/Controller/HelloController.php class HelloController extends Controller { public function fancyAction($name, $color) { // create some object, based on the $color variable $object = ...; return $this->render('HelloBundle:Hello:fancy', array( 'name' => $name, 'object' => $object )); } // ... }
  • Les aides de vue sont des objets accessibles depuis les templates et qui permettent de simpli er la logique d’affichage
  • Générer une URL avec le router helper <a href="<?php echo $view['router']->generate('hello', array( 'name' => 'Thomas')) ?>">Greet Thomas!</a> Inclure des feuilles de style <head> <!-- ... --> <?php $view['stylesheets']->add('css/styles.css') ?> <?php echo $view['stylesheets'] ?> </head>
  • Inclure des javascripts <head> <!-- ... --> <?php $view['javascripts']->add('js/libraries.js') ?> <?php echo $view['javascripts'] ?> </head> Manipuler des ressource web (images, ash…) <img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" src=""/> Traduire des chaînes de l’interface <?php echo $view['translator']->trans('Symfony is %what%!', array( '%what%' => 'awesome')) ?>
  • Con guration
  • 3 formats de con guration PHP YAML XML
  • Quel format choisir ? Avantages Inconvénients XML Validation Verbeux Complétion dans les EDIs Long à écrire Facile à analyser YAML Concis Besoin du composant YAML Facile à lire Pas de validation Facile à modi er Pas d’autocomplétion PHP Flexible Pas de validation Plus facile à manipuler
  • Con guration en YML # app/config/routing.php homepage: pattern: / defaults: { _controller: FrameworkBundle:Default:index } hello: resource: HelloBundle/Resources/config/routing.yml Import  d’une  autre  configura0on  
  • Con guration en PHP # app/config/routing.php use SymfonyComponentRoutingRouteCollection; use SymfonyComponentRoutingRoute; $collection = new RouteCollection(); $collection->addRoute('homepage', new Route('/', array( '_controller' => 'FrameworkBundle:Default:index', ))); $collection->addCollection( $loader->import("HelloBundle/Resources/config/routing.php") ); Import  d’une  autre  configura0on   return $collection;
  • Con guration en XML # app/config/routing.xml <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://www.symfony-project.org/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.symfony-project.org/schema/routing http:// www.symfony-project.org/schema/routing/routing-1.0.xsd"> <route id="homepage" pattern="/"> <default key="_controller">FrameworkBundle:Default:index</default> </route> <import resource="HelloBundle/Resources/config/routing.xml" /> </routes> Import  d’une  autre  configura0on  
  • Import de chiers INI # app/config/config_dev.yml imports: - { resource: config.yml } - { resource: custom.ini} zend.logger: priority: debug path: %kernel.root_dir%/logs/%kernel.environment%.log # app/config/custom.ini [parameters] dice.min = 1 dice.max = 6
  • Accès à la con guration depuis le code public function diceAction() { // ... $min = (int) $this->container->getParameter('dice.min'); $max = (int) $this->container->getParameter('dice.max'); // ... }
  • Outils de Débogage
  • Parce qu’il est important pour un développeur d’identi er rapidement les bogues et les problèmes !!!
  • Web Debug Toolbar
  • Logs
  • Traces d’exception Trace  de  l’excep0on   courrante   Trace  pour  une   InvalidArgumentExcep0on   404  Status  Code  
  • Afficher  /  masquer  la  trace  d’une   excep0on   Logs  enregistrés   Lien  vers  le  profiler  
  • Pro ler
  • Trace  de  l’excep0on   Recherche  dans  les  logs  
  • Requêtes SQL
  • Extensibilité
  • http://www.symfony2bundles.org
  • Enregistrement de bundles # app/AppKernel.php class AppKernel extends Kernel { // ... public function registerBundles() { $bundles = array( // ... // Register third party bundles new BundleTwitterBundleTwitterBundle(), new BundleForumBundleForumBundle() ); // ... return $bundles; } }
  • DBAL & ORM Doctrine2
  • •  Abstraction de base de données relationnelles •  Performance •  Plus de magie •  Manipulation de vrais objets PHP (POPO) •  Génération de code •  Adapteur MongoDB disponible
  • ๏  Con gurer la connexion BDD en YAML # app/config/config.yml doctrine.dbal: dbname: Blog user: root password: ~ doctrine.orm: ~ ๏  Con gurer la connexion BDD dans Apache // web/.htaccess or in the vhost configuration SetEnv SYMFONY__DOCTRINE__DBAL__USERNAME "root" SetEnv SYMFONY__DOCTRINE__DBAL__PASSWORD "secret"
  • ๏  Dé nition d’une entité (table) à l’aide d’une classe PHP namespace ApplicationBlogBundleEntity; /** * @Entity(repositoryClass="ApplicationBlogBundleModelBlogPostRepository") * @Table(name="blog_post") */ Annota0ons   class BlogPost { /** * @Id @Column(type="integer") * @GeneratedValue(strategy="IDENTITY") */ protected $id; /** @Column(length=100) */ protected $title; /** @Column(type="text") */ protected $content; }
  • ๏  Génération de la base de données à partir des classes PHP $ php app/console doctrine:database:create $ php app/console doctrine:schema:create ๏  Chargement des données de test $ php app/console doctrine:data:load
  • ๏  Les données de test sont écrites en pur PHP # src/Application/BlogBundle/Resources/data/fixtures/doctrine/fixtures.php use ApplicationBlogBundleEntityBlogPost; $post1 = new BlogPost(); $post1->setTitle('My first blog post'); $post1->setContent('Lorem ipsum dolor sit amet...'); $post2 = new BlogPost(); $post2->setTitle('My second blog post'); $post2->setContent('Lorem ipsum dolor sit amet...'); $post3 = new BlogPost(); $post3->setTitle('My third blog post'); $post3->setContent('Lorem ipsum dolor sit amet...');
  • ๏  Ecrire des requêtes DQL dans un modèle Doctrine # src/Application/BlogBundle/Model/BlogPostRepository.php namespace ApplicationBlogBundleModel; use DoctrineORMEntityRepository; class BlogPostRepository extends EntityRepository { public function getHomepagePosts() { $query = $this->_em->createQuery(' SELECT u FROM BlogBundle:BlogPost u ORDER BY u.id DESC '); return $query->getResult(); } }
  • ๏  Interroger la base de données à l’aide du Modèle Doctrine # src/Application/BlogBundle/Controller/BlogController.php // ... class BlogController extends Controller { public function indexAction() { $em = $this['doctrine.orm.entity_manager']; $posts = $em->getRepository('BlogBundle:BlogPost') ->getHomepagePosts(); return $this->render('BlogBundle:Blog:index', array( 'posts' => $posts )); } // ... }
  • Emails Swift Mailer
  • •  API Orientée Objet Open-Source •  Support des connexions SMTP •  Support des pièces jointes •  Support des formats de mails (text, html…) •  Gestion des les d’attente (spools) •  Facile à con gurer et à étendre avec des plugins
  • Con gurer Swift Mailer # app/config/config.yml swift.mailer: transport: smtp encryption: ssl auth_mode: login host: smtp.gmail.com username: your_username password: your_password
  • Envoyer un Email public function indexAction($name) { Récupéra0on  du  service  d’envoi  de  mails   $mailer = $this['mailer']; $message = Swift_Message::newInstance() ->setSubject('Hello Email') ->setFrom('send@example.com') ->setTo('recipient@example.com') ->setBody($this->renderView('HelloBundle:Hello:email', array( 'name' => $name ))); Généra0on  du  corps  du  mail  à  l’aide   d’un  template  et  de  la  méthode   $mailer->send($message); renderView()   return $this->render(...); }
  • Tests Automatisés PHPUnit
  • •  Tests Unitaires et Couverture de Code •  Garantir la qualité du code •  Eviter les bugs et les régressions •  Documenter le code •  Industrialiser et professionnaliser les développements
  • ๏  Exemple de script de tests unitaires dans Symfony2 # src/Application/BlogBundle/Tests/Entity/BlogPostTest.php namespace ApplicationBlogBundleTestsEntity; use ApplicationBlogBundleEntityBlogPost; class BlogPostTest extends PHPUnit_Framework_TestCase { public function testTitleIsSlugifiedOnce() { $slug = 'symfony2-rules-the-world'; $post = new BlogPost(); $post->setTitle('Symfony2 rules the world'); $this->assertEquals($slug, $post->getSlug()); // Slug doesn't change when it's already set $post->setTitle('An other title'); $this->assertEquals($slug, $post->getSlug()); } }
  • •  Tests fonctionnels •  Simuler des scénarios de navigation •  Simuler un client Web (navigateur) •  Véri er que l’application respecte le cahier des charges
  • ๏  Exemple de script de tests fonctionnels dans Symfony2 class BlogControllerTest extends WebTestCase { // ... public function testAddComment() { $this->client->followRedirects(); $crawler = $this->client->request('GET', '/'); // Get the first link to a post $link = $crawler->filter('h2.post a')->first()->link(); // Click on the link and check there are two comments $crawler = $this->client->click($link); $this->assertTrue($crawler->filter('.comment')->count() == 2); } }
  • ๏  Simuler des requêtes GET $crawler = $client->request('GET', '/hello/Fabien'); ๏  Simuler des requêtes POST $client->request('POST', '/submit', array('name' => 'Fabien') ๏  Simuler des uploads de chiers en POST $client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => '/path/to/photo') );
  • ๏  Simuler une requête HTTP DELETE avec des entêtes $client->request('DELETE', '/post/12', array(), array(), array( 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word' )); ๏  Désactiver / activer les redirections HTTP $client->followRedirects(false); $client->followRedirect(); ๏  Insoler le client dans un processus séparé $client->insulate();
  • ๏  Naviguer dans l’historique comme dans un navigateur web $client->back(); $client->forward(); $client->reload(); ๏  Réinitialiser le Client $client->restart();
  • ๏  Parcourir le DOM avec le DOM Crawler // Nodes that match the CSS selector $crawler->filter('h1'); // Nodes that match the XPath expression $crawler->filterXpath('h1'); // Node for the specified index $crawler->eq(1); // First node $crawler->first(); // Last node $crawler->last();
  • // Siblings $crawler->siblings(); // All following siblings $crawler->nextAll(); // All preceding siblings $crawler->previousAll(); // Parent nodes $crawler->parents(); // Children $crawler->children(); // Nodes for which the callable, a lambda, returns true $crawler->reduce($lambda);
  • ๏  Extraire des données sur des noeuds // Returns the attribute value for the first node $crawler->attr('class'); // Returns the node value for the first node $crawler->text(); // Extracts an array of attributes for all nodes // (_text returns the node value) $crawler->extract(array('_text', 'href')); // Executes a lambda for each node // and return an array of results $data = $crawler->each(function ($node, $i) { return $node->getAttribute('href'); });
  • ๏  Simuler des clics sur des liens ou boutons $crawler->selectLink('Click here'); $link = $crawler->link(); $client->click($link); $links = $crawler->links(); ๏  Poster des formulaires // Select the submit button of a form $crawler->selectButton('submit'); // Get a form instance $form = $crawler->form(); // Override the default form values $form = $crawler->form(array( 'name' => 'Fabien', 'like_symfony' => true, ));
  • Performances
  • •  PHP 5.3.2 minimum •  “ Cachy framework “ •  Cache HTTP & Proxy cache (ESI) •  Faible consommation mémoire •  Tous les services sont chargés à la demande
  • // src/Application/BlogBundle/Resources/views/layout.php $view['actions']->output('BlogBundle:Blog:lastComments', array(), array( 'standalone' => false )); If  the  standalone  parameter  is  set   to  false,  Symfony2  will  render  the   HTML  content  
  • // src/Application/BlogBundle/Resources/views/layout.php $view['actions']->output('BlogBundle:Blog:lastComments', array(), array( 'standalone' => true )); If  the  standalone  parameter  is  set  to   true  and  if  there  is  a  compa0ble  proxy   <esi:include  src="..."  />   cache,  Symfony2  will  render  an  ESI  tag  
  • Edge Side Includes aka ESI...
  • Edge Side Includes
  • Questions ?
  • Trainings Business Unit trainings@sensio.com Sensio S.A. 92-98, boulevard Victor Hugo 92 115 Clichy Cedex FRANCE Tél. : +33 1 40 99 80 80 www.sensiolabs.com - www.symfony-project.org - trainings.sensiolabs.com