Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Commander une application Symfony par le texte

Commander une application Symfony par le texte

Quand une application comporte des centaines de fonctionnalités et des millions de lignes en base de données, il est souvent fastidieux d'accéder à une information. Il faut choisir le bon item dans un menu, chercher dans une liste, cliquer sur modifier, accéder à un formulaire pour enfin pouvoir modifier une donnée. Nous allons voir comment on peut facilement ajouter à une application Symfony une UI différente, une interface de commande par texte avec autocompletion.

Richard Hanna

July 17, 2018
Tweet

More Decks by Richard Hanna

Other Decks in Technology

Transcript

  1. Commander au clavier
    une application Symfony
    SfPot Paris, mardi 17 juillet 2018

    View full-size slide

  2. @ r i c h a rd h a n n a
    D É V E L O P P E U R

    View full-size slide

  3. @PodcastEcho
    Son rôle au sein de Symfony
    Son parcours et projets Opensource
    Recrutement dans la tech Développeuse au parcours atypique
    UX mobile et PWA Analyse et 1er bilan de Parcoursup

    View full-size slide

  4. Commander au clavier
    une application Symfony

    View full-size slide

  5. Interface d'administration d'une application classique. Il y a des listes, des
    boutons, des menus. Il y a des centaines de fonctionnalités et des millions de
    lignes en base de données.

    Accéder à une ressource peut parfois être fastidieux : plusieurs clics pour
    accéder à une fonctionnalité.

    View full-size slide

  6. L'application peut ressembler à une ville bruyante et effervescente

    View full-size slide

  7. Une autre UI est possible ?

    View full-size slide

  8. use Symfony\Component\Routing\Route;

    use Symfony\Component\Routing\RouterInterface;

    class AllRoutesResolver

    {

    /** @var RouterInterface */
    private $router;

    public function __construct(RouterInterface $router)

    {

    $this->router = $router;

    }

    /**
    * @return Route[]
    */
    public function getAllRoutes(): array
    {

    return $this->router->getRouteCollection()->all();

    }

    }

    Récupérer toutes les routes
    de l'application

    View full-size slide

  9. return array_filter(

    $this->router->getRouteCollection()->all(),

    function (Route $route) {

    return \in_array('GET', $route->getMethods(), true);

    }

    );

    Filtrer les routes en GET

    View full-size slide

  10. admin_user_list ➡ User list
    admin_user_create ➡ User create
    admin_generate_invoice_for_order ➡ Generate invoice for Order
    Humaniser les noms de route

    View full-size slide

  11. class RouteGenerator

    {

    /** @var RouterInterface */
    private $router;

    public function __construct(RouterInterface $router)

    {

    $this->router = $router;

    }

    public function generateUrl(string $routeName, array $parameters): string

    {

    return $this->router->generate($routeName, $parameters);

    }

    }

    Générer l'url

    View full-size slide

  12. array:140 [▼
    0 => ResultView {#1388 ▼
    +label: "User list"
    +routeName: "admin_user_list"
    +url: "/user/list"
    }
    1 => ResultView {#1403 ▶}
    2 => ResultView {#1402 ▶}
    ...
    L'ensemble des urls générées

    View full-size slide

  13. Deviner des paramètres de route

    View full-size slide

  14. array:140 [▼
    0 => ResultView {#1388 ▼
    +label: "User list"
    +routeName: "admin_user_list"
    +url: "/user/list"
    +parameters: array:0 []
    }
    1 => ResultView {#1389 ▼
    +label: "User update"
    +routeName: "admin_user_update"
    +url: null
    +parameters: array:1 [▼
    "user" => null
    ]
    }

    Routes avec des paramètres

    View full-size slide

  15. User update_
    ➡ User update Korben DALLAS
    ➡ User update Leeloo Ekbat De Sebat
    ➡ User update Cornelius
    Suggestion de résultats

    View full-size slide

  16. # Routing
    admin_user_update:
    path: /user/update/{user}

    methods: [GET, POST]

    requirements:
    user: \d+

    defaults: { _controller: AdminBundle:User:update }

    # Controller
    class UserController extends Controller

    {

    public function updateAction(Request $request, User $user): Response

    {

    ou

    class UpdateUserAction

    {

    public function __invoke(Request $request, User $user): Response

    {

    # Get Route requirements
    public function getRequirements(Route $route): array
    {

    return array_keys($route->getRequirements());

    }
    Récupérer les requirements d'une route

    View full-size slide

  17. use Symfony\Component\HttpFoundation\Request;

    use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;

    use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

    use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface;

    use Symfony\Component\Routing\Route;

    class RouteGetter

    {

    /** ... */
    public function __construct(

    ControllerResolverInterface $controllerResolver,

    ArgumentMetadataFactoryInterface $argumentMetadataFactory

    ) {

    $this->controllerResolver = $controllerResolver;

    $this->argumentMetadataFactory = $argumentMetadataFactory;

    }

    /** @return ArgumentMetadata[] */
    public function getArgumentsMetadata(Route $route): array
    {

    $request = new Request([], [], ['_controller' => $route->getDefault('_controller')]);

    $controller = $this->controllerResolver->getController($request);

    return $this->argumentMetadataFactory->createArgumentMetadata($controller);

    }

    }

    Récupérer les metadata du controller d'une route

    View full-size slide

  18. array:2 [▼
    0 => ArgumentMetadata {#2146 ▼
    -name: "request"
    -type: "Symfony\Component\HttpFoundation\Request"
    -isVariadic: false
    -hasDefaultValue: false
    -defaultValue: null
    -isNullable: false
    }
    1 => ArgumentMetadata {#2151 ▼
    -name: "user"
    -type: "App\Domain\Model\User"
    -isVariadic: false
    -hasDefaultValue: false
    -defaultValue: null
    -isNullable: false
    }
    ]
    Récupérer les metadata du
    controller d'une route

    View full-size slide

  19. class UserResolver implements ResolverInterface

    {

    /** ... */
    /** ResultView[] */
    public function resolve(ResultView $resultView): array
    {

    $enabledUsers = $this->userRepository->getEnabledUser();

    $resultViews = [];

    foreach ($enabledUsers as $user) {

    $resultViews[] = new ResultView($user->getFullName(), ['user' => $user->getId()]);

    }

    return $resultViews;

    }

    }

    Custom resolver

    View full-size slide

  20. array:2 [▼
    0 => ResultView {#1395 ▼
    +label: "User update Korben DALLAS"
    +routeName: "admin_user_update"
    +url: "/user/update/42"
    +parameters: array:2 [▼
    "user" => 42
    "_locale" => "en"
    ]
    }
    1 => ResultView {#1403 ▼
    +label: "User update Leeloo Ekbat De Sebat"
    +routeName: "admin_user_update"
    +url: "/user/update/1337"
    +parameters: array:2 [▶]
    }
    Custom resolver

    View full-size slide

  21. Resolver Doctrine ?

    View full-size slide

  22. use Doctrine\Common\Persistence\ManagerRegistry;

    class DoctrineResolver {

    public function __construct(ManagerRegistry $managerRegistry) {

    $this->managerRegistry = $managerRegistry;

    }

    public function resolve(ResultView $resultView, string $paramName, string $className): array {

    $entityManager = $this->managerRegistry->getManagerForClass($className);

    if (null === $entityManager) { return []; }

    $objects = $entityManager->getRepository($className)->findAll();

    $resultViews = [];

    foreach ($objects as $object) {

    $parameters = $resultView->parameters;

    $parameters[$paramName] = $object->getId();

    $resultViews[] = new ResultView(

    sprintf('%s %s', $resultView->label, $object),

    $resultView->routeName,

    $resultView->url,

    $parameters

    );

    }

    return $resultViews;

    }
    Resolver Doctrine
    class User

    {

    public function __toString(): string

    {

    return $this->getDisplayName();

    }

    View full-size slide

  23. Traduction des noms de route

    View full-size slide

  24. # Routing
    admin_user_list:
    path: /user/list/{user}

    admin_user_create:
    path: /user/create/{user}

    admin_user_update:
    path: /user/update/{user}

    # humanized_routes.en.yml
    admin_user_list: User List
    admin_user_create: User Create
    admin_user_update: User Update
    # humanized_routes.fr.yml
    admin_user_list: Utilisateur Lister
    admin_user_create: Utilisateur Créer
    admin_user_update: Utilisateur Modifier
    Traduction des noms de route

    View full-size slide

  25. use Symfony\Component\Translation\TranslatorBagInterface;

    use Symfony\Component\Translation\TranslatorInterface;

    class TranslateRouteName

    {

    public function __construct(

    TranslatorInterface $translator,

    TranslatorBagInterface $translatorBag

    ) {

    $this->translator = $translator;

    $this->translatorBag = $translatorBag;

    }

    public function handle(string $routeName, string $locale): string

    {

    $catalogue = $this->translatorBag->getCatalogue($locale);



    return $catalogue->has($routeName, 'humanized_routes')

    ? $this->translator->trans($routeName, [], 'humanized_routes', $locale)

    : $this->humanizeRouteName($routeName);

    }

    protected function humanizeRouteName(string $routeName): string

    {

    return ucfirst(str_replace(['admin_', '_'], ['', ' '], $routeName));

    }

    }
    Traduction des noms de route
    App\ActionsBot\Resolver\TranslateRouteName:
    arguments:
    - '@translator'
    - '@translator'

    View full-size slide

  26. ✓ Nouvelle UX / UI basées sur les routes
    ✓ Rapidité++ efficacité++
    ✓ Commandes auto-générées ou personnalisées
    ✓ Générer une API à partir du routing ?
    Bilan

    View full-size slide

  27. ⤬ inversion langage : "Utilisateur Modifier"
    au lieu de "Modifier utilisateur"
    ⤬ proposer une recherche en langage naturel
    ⤬ savoir quoi chercher
    ⤬ savoir comment chercher
    Inconvénients

    View full-size slide

  28. One more thing...

    View full-size slide

  29. Commander par la voix ?

    View full-size slide

  30. Commander par la voix

    View full-size slide

  31. Web Speech API
    Speech Synthesis
    Speech Recognition

    View full-size slide

  32. var recognition = new SpeechRecognition();

    recognition.continuous = true;

    recognition.lang = 'fr-FR';

    recognition.onresult = function (event) {

    for (i = event.resultIndex; i < event.results.length; i++) {

    var result = event.results[i][0];

    console.log(result.transcript + ': ' + result.confidence);

    }

    };

    recognition.start();
    Speech recognition

    View full-size slide

  33. Démo ?
    https://www.google.com/intl/en/chrome/demos/speech.html

    View full-size slide

  34. @ r i c h a rd h a n n a
    D É V E L O P P E U R
    @PodcastEcho
    Commander au clavier
    une application Symfony
    grâce au Routing!

    View full-size slide