Symfony2 - Jobeet - Jour 12 - Le paquet Admin

1 1 1 1 1 1 1 1 1 1 Rating 4.00 (2 Votes)
Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn

Avec ce que nous avons ajouté au jour 11 (Symfony2 - Jobeet - Jour 11 - Testez vos formulaires), l'application est maintenant pleinement utilisable par les chercheurs d'emploi et les recruteurs. Il est temps de parler un peu de la section admin de notre application. Aujourd'hui, grâce au paquet SonataAdminBundle, nous allons développer une interface complète d'administration pour Jobeet en moins d'une heure.

Installation du paquet Admin

Commencez par télécharger SonataAdminBundle et ses dépendances :

composer require sonata-project/admin-bundle
Please provide a version constraint for the sonata-project/admin-bundle requirement: *

Pour installer la dernière version de la SonataAdminBundle et ses dépendances, saisir * quand la question vous sera posée.

Nous aurons aussi besoin d'installer le SonataDoctrineORMADminBundle :

composer require sonata-project/doctrine-orm-admin-bundle
Please provide a version constraint for the sonata-project/doctrine-orm-admin-bundle requirement: *

Maintenant, nous devons déclarer ces nouveaux forfaits et les dépendances, il faut aller dans votre fichier AppKernel.php et ajoutez le code suivant :

  • app/AppKernel.php
// ...
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new Sonata\AdminBundle\SonataAdminBundle(),
            new Sonata\CoreBundle\SonataCoreBundle(),
            new Sonata\BlockBundle\SonataBlockBundle(),
            new Sonata\jQueryBundle\SonatajQueryBundle(),
            new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
            new Knp\Bundle\MenuBundle\KnpMenuBundle(),
        );
    }

// ...

Vous aurez aussi besoin de modifier votre fichier app/config/config.yml. Ajoutez ce qui suit à la fin :

  • app/config/config.yml
# ...
sonata_admin:
    title: Jobeet Admin

sonata_block:
    default_contexts: [cms]
    blocks:
        sonata.admin.block.admin_list:
            contexts:   [admin]

        sonata.block.service.text:
        sonata.block.service.action:
        sonata.block.service.rss:

Aussi, recherchez l'entrée translator et décommentez-la si elle est commentée :

  • app/config/config.yml
# ...
framework:
    # ...
    translator: { fallback: %locale%}
    # ...
#...

Pour que votre application fonctionne, vous devez importer les routes d'administration dans le fichier de routage de l'application :

  • app/config/routing.yml
admin:
    resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
    prefix: /admin

_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin

# ...

Maintenant, installez les assets des paquets :

php app/console assets:install web

N'oubliez pas de vider le cache :

php app/console cache:clear --env=dev
php app/console cache:clear --env=prod

Vous devriez maintenant être en mesure d'accéder à l'interface d'administration en utilisant l'URL suivante : http://jobeet.local/app_dev.php/admin/dashboard

Le contrôleur CRUD

Le contrôleur CRUD contient les actions de base CRUD. Elle est liée à une classe Admin en mappant le nom du contrôleur à l'instance Admin valide. Toutes ou partie des actions peuvent être remplacées en fonction des besoins du projet. Le contrôleur utilise la classe Admin pour construire les différentes actions. A l'intérieur du contrôleur, l'objet Admin est accessible via la propriété de configuration.

Maintenant créez un contrôleur pour chaque entité. D'abord pour Category:

  • src/Erlem/JobeetBundle/Controller/CategoryAdminController.php
<?php

namespace Erlem\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;

class CategoryAdminController extends Controller
{
    // Your code will be here
}

Et maintenant, pour Job :

  • src/Erlem/JobeetBundle/Controller/JobAdminController.php
<?php

namespace Erlem\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;

class JobAdminController extends Controller
{
    // Your code will be here
}

Création de la classe Admin

La classe Admin représente la cartographie de votre modèle et sections d'admininstration (formulaires, listes, affichages). La meilleure façon de créer une classe d'administration de votre modèle est d'étendre la classe Sonata\AdminBundle\Admin\Admin. Nous allons créer des classes d'admin dans le dossier admin de notre paquet. Pour les catégories :

  • src/Erlem/JobeetBundle/Admin/CategoryAdmin.php
<?php

namespace Erlem\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;

class CategoryAdmin extends Admin
{
    // Your code will be here
}

Et pour les offres :

  • src/Erlem/JobeetBundle/Admin/JobAdmin.php
<?php

namespace Erlem\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Erlem\JobeetBundle\Entity\Job;

class JobAdmin extends Admin
{
    // Your code will be here
}

Maintenant, nous devons ajouter chaque classe admin dans le fichier de configuration services.yml:

  • src/Erlem/JobeetBundle/Resources/config/services.yml
services:
    erlem.jobeet.admin.category:
        class: Erlem\JobeetBundle\Admin\CategoryAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: jobeet, label: Categories }
        arguments:
            - ~
            - Erlem\JobeetBundle\Entity\Category
            - 'ErlemJobeetBundle:CategoryAdmin'

    erlem.jobeet.admin.job:
        class: Erlem\JobeetBundle\Admin\JobAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: jobeet, label: Jobs }
        arguments:
            - ~
            - Erlem\JobeetBundle\Entity\Job
            - 'ErlemJobeetBundle:JobAdmin'

A ce stade, nous pouvons voir dans le tableau de bord le groupe Jobeet et à l'intérieur les modules Job et Category, avec leurs liens respectifs d'ajout et de liste. Allez à l'adresse http://jobeet.local/app_dev.php/admin/dashboard

01-111-symfony2-jobeet-jour-12-le-paquet-admin

Configuration des classes Admin

À ce stade, si l'on suit n'importe quel lien rien ne se passera. C'est parce que nous n'avons pas configuré les champs qui appartiennent à la liste et au formulaire. Faisons une configuration de base, d'abord pour les catégories:

  • src/Erlem/JobeetBundle/Admin/CategoryAdmin.php
namespace Erlem\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;

class CategoryAdmin extends Admin
{
    // setup the default sort column and order
    protected $datagridValues = array(
        '_sort_order' => 'ASC',
        '_sort_by' => 'name'
    );

    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('name')
            ->add('slug')
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('name')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('name')
            ->add('slug')
        ;
    }
}

Et maintenant pour les offres:

  • src/Erlem/JobeetBundle/Admin/JobAdmin.php
namespace Erlem\JobeetBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Erlem\JobeetBundle\Entity\Job;

class JobAdmin extends Admin
{
    // setup the defaut sort column and order
    protected $datagridValues = array(
        '_sort_order' => 'DESC',
        '_sort_by' => 'created_at'
    );

    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('category')
            ->add('type', 'choice', array('choices' => Job::getTypes(), 'expanded' => true))
            ->add('company')
            ->add('file', 'file', array('label' => 'Company logo', 'required' => false))
            ->add('url')
            ->add('position')
            ->add('location')
            ->add('description')
            ->add('how_to_apply')
            ->add('is_public')
            ->add('email')
            ->add('is_activated')
        ;
    }

    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
            ->add('category')
            ->add('company')
            ->add('position')
            ->add('description')
            ->add('is_activated')
            ->add('is_public')
            ->add('email')
            ->add('expires_at')
        ;
    }

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('company')
            ->add('position')
            ->add('location')
            ->add('url')
            ->add('is_activated')
            ->add('email')
            ->add('category')
            ->add('expires_at')
            ->add('_action', 'actions', array(
                'actions' => array(
                    'view' => array(),
                    'edit' => array(),
                    'delete' => array(),
                )
            ))
        ;
    }

    protected function configureShowField(ShowMapper $showMapper)
    {
        $showMapper
            ->add('category')
            ->add('type')
            ->add('company')
            ->add('webPath', 'string', array('template' => 'ErlemJobeetBundle:JobAdmin:list_image.html.twig'))
            ->add('url')
            ->add('position')
            ->add('location')
            ->add('description')
            ->add('how_to_apply')
            ->add('is_public')
            ->add('is_activated')
            ->add('token')
            ->add('email')
            ->add('expires_at')
        ;
    }
}

Pour l'action show, nous avons utilisé un template personnalisé pour afficher le logo de l'entreprise :

  • src/Erlem/JobeetBundle/Resources/views/JobAdmin/list_image.html.twig
<tr>
    <th>Logo</th>
    <td><img src="/{{ asset(object.webPath) }}" /></td>
</tr>

Grâce à cela, nous avons créé un module d'administration de base avec les opérations CRUD pour nos offres et catégories. Parmi les fonctionnalités que vous trouverez lors de son utilisation:

  • La liste des objets est paginée
  • La liste peut être triée
  • La liste peut être filtrée
  • Les objets peuvent être créés, modifiés et supprimés
  • Les objets sélectionnés peuvent être supprimés par lot
  • La validation du formulaire est activée
  • Les messages flash donnent une rétroaction immédiate à l'utilisateur

Lister les Catégories : http://jobeet.local/app_dev.php/admin/erlem/jobeet/category/list

02-111-symfony2-jobeet-jour-12-le-paquet-admin

Lister les Jobs : http://jobeet.local/app_dev.php/admin/erlem/jobeet/job/list

03-111-symfony2-jobeet-jour-12-le-paquet-admin

Actions par lot (batch)

Les actions batch sont des actions déclenchées sur un ensemble de modèles sélectionnés (tous ou seulement un sous-ensemble spécifique). Vous pouvez facilement ajouter une action batch personnalisée dans la liste. Par défaut, l'action delete permet de supprimer plusieurs entrées à la fois.

Pour ajouter une nouvelle action batch, nous devons remplacer la méthode getBatchActions de la classe Admin. Nous allons définir ici une nouvelle action extend :

  • src/Erlem/JobeetBundle/Admin/JobAdmin.php
// ...

public function getBatchActions()
{
    // retrieve the default (currently only the delete action) actions
    $actions = parent::getBatchActions();

    // check user permissions
    if($this->hasRoute('edit') && $this->isGranted('EDIT') && $this->hasRoute('delete') && $this->isGranted('DELETE')) {
        $actions['extend'] = array(
            'label'            => 'Extend',
            'ask_confirmation' => true // If true, a confirmation will be asked before performing the action
        );

    }

    return $actions;
}

La méthode batchActionExtend de JobAdminController sera exécutée pour atteindre la logique de base. Les modèles sélectionnés sont transmis à la méthode par le biais d'un argument requête qui les récupére. Si, pour une raison quelconque, il est logique d'effectuer votre action batch sans la méthode de sélection par défaut (par exemple vous avez défini une autre façon, au niveau du template, pour sélectionner le modèle à moindre granularité), la requête passée est null.

  • src/Erlem/JobeetBundle/Controller/JobAdminController.php
namespace Erlem\JobeetBundle\Controller;

use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery as ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

class JobAdminController extends Controller
{
    public function batchActionExtend(ProxyQueryInterface $selectedModelQuery)
    {
        if ($this->admin->isGranted('EDIT') === false || $this->admin->isGranted('DELETE') === false) {
            throw new AccessDeniedException();
        }

        $modelManager = $this->admin->getModelManager();

        $selectedModels = $selectedModelQuery->execute();

        try {
            foreach ($selectedModels as $selectedModel) {
                $selectedModel->extend();
                $modelManager->update($selectedModel);
            }
        } catch (\Exception $e) {
            $this->get('session')->getFlashBag()->add('sonata_flash_error', $e->getMessage());

            return new RedirectResponse($this->admin->generateUrl('list',$this->admin->getFilterParameters()));
        }

        $this->get('session')->getFlashBag()->add('sonata_flash_success',  sprintf('The selected jobs validity has been extended until %s.', date('m/d/Y', time() + 86400 * 30)));

        return new RedirectResponse($this->admin->generateUrl('list',$this->admin->getFilterParameters()));
    }
}

Ajoutons une nouvelle action batch qui va supprimer toutes les offres qui n'ont pas été activées par le recruteur depuis plus de 60 jours. Pour cette action, nous n'avons pas besoin de sélectionner des offres d'emploi de la liste parce que la logique de l'action sera de rechercher les enregistrements correspondants et les supprimer.

  • src/Erlem/JobeetBundle/Admin/JobAdmin.php
// ...

public function getBatchActions()
{
    // retrieve the default (currently only the delete action) actions
    $actions = parent::getBatchActions();

    // check user permissions
    if($this->hasRoute('edit') && $this->isGranted('EDIT') && $this->hasRoute('delete') && $this->isGranted('DELETE')){
        $actions['extend'] = array(
            'label'            => 'Extend',
            'ask_confirmation' => true // If true, a confirmation will be asked before performing the action
        );

        $actions['deleteNeverActivated'] = array(
            'label'            => 'Delete never activated jobs',
            'ask_confirmation' => true // If true, a confirmation will be asked before performing the action
        );
    }

    return $actions;
}

En plus de créer l'action batchActionDeleteNeverActivated, nous allons créer une nouvelle méthode dans JobAdminController, batchActionDeleteNeverActivatedIsRelevant, qui est exécutée avant toute confirmation, pour s'assurer qu'y a quelque chose pour confirmer. Dans notre cas elle renverra toujours true parce que le choix des offres qui seront supprimées est géré par la logique dans la méthode JobRepository::cleanup()

  • src/Erlem/JobeetBundle/Controller/JobAdminController.php
// ...

public function batchActionDeleteNeverActivatedIsRelevant()
{
    return true;
}

public function batchActionDeleteNeverActivated()
{
    if ($this->admin->isGranted('EDIT') === false || $this->admin->isGranted('DELETE') === false) {
        throw new AccessDeniedException();
    }

    $em = $this->getDoctrine()->getManager();
    $nb = $em->getRepository('ErlemJobeetBundle:Job')->cleanup(60);

    if ($nb) {
        $this->get('session')->getFlashBag()->add('sonata_flash_success',  sprintf('%d never activated jobs have been deleted successfully.', $nb));
    } else {
        $this->get('session')->getFlashBag()->add('sonata_flash_info',  'No job to delete.');
    }

    return new RedirectResponse($this->admin->generateUrl('list',$this->admin->getFilterParameters()));
}

C'est tout pour aujourd'hui ! Demain, nous allons voir comment sécuriser la section admin avec un nom d'utilisateur et un mot de passe. Ce sera l'occasion pour parler de la sécurité de Symfony2.


Symfony2 - Jour 11 - Testez vos formulaires
[Sommaire] Symfony2 - Jour 13 - Sécurité >


Pour ce tutoriel, je me suis inspiré du tutoriel Symfony2 Jobeet du site IntelligentBee

Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn