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.

C872075a10c9575978184134d091baec?s=128

Richard Hanna

July 17, 2018
Tweet

Transcript

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

    juillet 2018
  2. @ r i c h a rd h a n

    n a D É V E L O P P E U R
  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
  4. Commander au clavier une application Symfony

  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é.
  6. L'application peut ressembler à une ville bruyante et effervescente

  7. Une autre UI est possible ?

  8. None
  9. None
  10. Chat bot ?

  11. Routing ?

  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
  13. None
  14. return array_filter( $this->router->getRouteCollection()->all(), function (Route $route) { return \in_array('GET', $route->getMethods(),

    true); } ); Filtrer les routes en GET
  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
  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
  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
  18. Démo

  19. Deviner des paramètres de route

  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
  21. User update_ ➡ User update Korben DALLAS ➡ User update

    Leeloo Ekbat De Sebat ➡ User update Cornelius Suggestion de résultats
  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
  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
  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
  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
  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
  27. Resolver Doctrine ?

  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(); }
  29. Traduction des noms de route

  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
  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'
  32. Démo

  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
  34. ⤬ inversion langage : "Utilisateur Modifier" au lieu de "Modifier

    utilisateur" ⤬ proposer une recherche en langage naturel ⤬ savoir quoi chercher ⤬ savoir comment chercher Inconvénients
  35. One more thing...

  36. Commander par la voix ?

  37. Commander par la voix

  38. Web Speech API Speech Synthesis Speech Recognition

  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
  40. Démo ? https://www.google.com/intl/en/chrome/demos/speech.html

  41. None
  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!