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 Slide

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

    View 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 Slide

  4. Commander au clavier
    une application Symfony

    View 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 Slide

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

    View Slide

  7. Une autre UI est possible ?

    View Slide

  8. View Slide

  9. View Slide

  10. Chat bot ?

    View Slide

  11. Routing ?

    View Slide

  12. 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 Slide

  13. View Slide

  14. return array_filter(

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

    function (Route $route) {

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

    }

    );

    Filtrer les routes en GET

    View Slide

  15. 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 Slide

  16. 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 Slide

  17. 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 Slide

  18. Démo

    View Slide

  19. Deviner des paramètres de route

    View Slide

  20. 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 Slide

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

    View Slide

  22. # 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 Slide

  23. 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 Slide

  24. 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 Slide

  25. 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 Slide

  26. 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 Slide

  27. Resolver Doctrine ?

    View Slide

  28. 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 Slide

  29. Traduction des noms de route

    View Slide

  30. # 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 Slide

  31. 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 Slide

  32. Démo

    View Slide

  33. ✓ 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 Slide

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

    View Slide

  35. One more thing...

    View Slide

  36. Commander par la voix ?

    View Slide

  37. Commander par la voix

    View Slide

  38. Web Speech API
    Speech Synthesis
    Speech Recognition

    View Slide

  39. 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 Slide

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

    View Slide

  41. View Slide

  42. @ 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 Slide