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

Symfony UX

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Symfony UX

Avatar for Fabien Potencier

Fabien Potencier

December 03, 2020
Tweet

More Decks by Fabien Potencier

Other Decks in Programming

Transcript

  1. A Strong Community A growing core team - 17 members

    Amazing support team - many Dedicated doc team - 7 members A growing community of developers - tons
  2. HttpKernelInterface has not changed since 2.0 Last change was before

    2.0 10 years ago Simple interfaces are powerful Standards are powerful BC policy is important namespace Symfony\Component\HttpKernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; interface HttpKernelInterface { const MASTER_REQUEST = 1; const SUB_REQUEST = 2; /** * Handles a Request to convert it to a Response. */ public function handle(Request $request, int $type = self::MASTER_REQUEST, bool $catch = true); }
  3. Flex enables Components to act as a Framework Standalone Components

    (think PEAR reinvented) FrameworkBundle as a set of default con f i gurations for core features Flex enables any package to feel like a Symfony core feature
  4. Security Bootstrap a new project the fast way $ symfony

    new demo $ composer req maker profiler orm validator form security $ vi docker-compose.yml $ docker-compose up -d $ symfony server:start -d $ symfony open:local version: '3' services: database: image: postgres:12-alpine environment: POSTGRES_USER: main POSTGRES_PASSWORD: main POSTGRES_DB: main ports: [5432] mailer: image: schickling/mailcatcher ports: [1025, 1080] Symfony CLI Docker Compose Composer & Flex
  5. The name of the security user class (e.g. User) [User]:

    > Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]: > Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email]: > Will this app need to hash/check user passwords? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server). Does this app need to hash/check user passwords? (yes/no) [yes]: > $ symfony console make:user Maker bundle generates code that you own
  6. What style of authentication do you want? [Empty authenticator]: [0]

    Empty authenticator [1] Login form authenticator > 1 The class name of the authenticator to create (e.g. AppCustomAuthenticator): > AppAuthenticator Choose a name for the controller class (e.g. SecurityController) [SecurityController]: > Do you want to generate a '/logout' URL? (yes/no) [yes]: > $ symfony console make:auth
  7. Creating a registration form for App\Entity\User Do you want to

    add a @UniqueEntity validation annotation on your User class to make sure duplicate accounts aren't created? (yes/no) [yes]: > Do you want to send an email to verify the user's email address after registration? (yes/no) [yes]: > [WARNING] We're missing some important components. Don't forget to install these after you're finished. composer require symfonycasts/verify-email-bundle symfony/mailer What email address will be used to send registration confirmations? e.g. [email protected]: > [email protected] What "name" should be associated with that email address? e.g. "Acme Mail Bot": > Demo Bot Do you want to automatically authenticate the user after registration? (yes/no) [yes]: > $ symfony console make:registration-form
  8. Security Bootstrap a new project the fast way $ symfony

    console make:migration $ symfony console doctrine:migrations:migrate -n
  9. Security Symfony 5.2+ features Passwordless login link authentication 
 https://symfony.com/doc/5.2/security/login_link.html

    2FA/MFA 
 https://github.com/scheb/2fa Help needed - https://github.com/symfony/symfony/issues/30914 Sudo mode and more
  10. Symfony CLI Docker Compose Symfony Flex RAD thanks to a

    tight integration 
 with the larger ecosystem Maker Bundle Docker Composer PHP Packages Webpack Webpack Encore The Magic Glue ? ?
  11. ??

  12. UX

  13. Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true], Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],

    ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], + Symfony\UX\Cropper\SymfonyCropperBundle::class => ['all' => true], + Symfony\UX\Dropzone\SymfonyDropzoneBundle::class => ['all' => true], + Symfony\UX\Lazyimg\SymfonyLazyimgBundle::class => ['all' => true], ]; config/bundles.php
  14. "popper.js": "^1.16.1", "regenerator-runtime": "^0.13.2", "sass-loader": "^7.0.1", - "webpack-notifier": "^1.6.0" +

    "webpack-notifier": "^1.6.0", + "@symfony/ux-cropper": "file:vendor/symfony/ux-cropper/assets", + "@symfony/ux-dropzone": "file:vendor/symfony/ux-dropzone/assets", + "@symfony/ux-lazyimg": "file:vendor/symfony/ux-lazyimg/assets", + "@symfony/ux-swup": "file:vendor/symfony/ux-swup/assets" }, "license": "UNLICENSED", "private": true, package.json
  15. { - "controllers": {}, - "autoimport": {}, "entrypoints": { "app":

    "app.js" + }, + "controllers": { + "@symfony/ux-cropper": { + "cropper": { "enabled": true, "webpackMode": "eager" } + }, + "@symfony/ux-dropzone": { + "dropzone": { "enabled": true, "webpackMode": "eager" } + }, + "@symfony/ux-lazyimg": { + "lazyimg": { "enabled": true, "webpackMode": "eager" } + }, + "@symfony/ux-swup": { + "swup": { "enabled": true, "webpackMode": "eager" } + } + }, + "autoimport": { + "@symfony/ux-cropper": { + "dist/style.js": { "enabled": true } + }, + "@symfony\/ux-dropzone": { + "dist/style.css": { "enabled": true } + } } assets/controllers.json
  16. +import { startStimulusApp } from '@symfony/stimulus-bridge'; +import '@symfony/autoimport'; + +export

    const app = startStimulusApp(require.context('./controllers', true, /\.(j|t)sx?$/)); assets/bootstrap.js // only needed for CDN's or sub-directory deploy //.setManifestKeyPrefix('build/') + // enables Symfony - Stimulus bridge + .enableStimulusBridge({ + entrypoints: './assets/controllers.json', + lockFile: './symfony.lock', + }) // When enabled, Webpack "splits" your files into smaller pieces for greater optimization. .splitEntryChunks() webpack.config.js +{ + "controllers": {}, + "autoimport": {}, + "entrypoints": { + "app": "app.js" + } +} assets/controllers.json
  17. composer req 
 symfony/ux-dropzone 
 symfony/ux-cropper 
 symfony/ux-lazyimg 
 symfony/ux-swup

    yarn && yarn encore dev Flex & Encore ❤ ❤ UX Backend World Frontend World
  18. DropZone use App\Entity\Comment; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; -use Symfony\Component\Form\Extension\Core\Type\FileType; use

    Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\Image; +use Symfony\UX\Dropzone\Form\DropzoneType; class CommentFormType extends AbstractType { @@ -21,7 +21,7 @@ class CommentFormType extends AbstractType ]) ->add('text') ->add('email', EmailType::class) - ->add('photo', FileType::class, [ + ->add('photo', DropzoneType::class, [ 'required' => false, 'mapped' => false, 'constraints' => [ CommentFormType.php
  19. Cropper use Symfony\UX\Cropper\Cropper; use Symfony\UX\Cropper\Form\CropperType; class ConferenceController extends AbstractController {

    /** * @Route("/{_locale<%app.supported_locales%>}/comment/{id}/crop", name="comment_crop") */ public function crop(Request $request, Comment $comment, Cropper $cropper, string $photoDir) { // Redirect to the conference page if no photo // Otherwise, allow the user to crop the uploaded image $crop = $cropper->createCrop($photoDir.'/'.$comment->getPhotoFilename()); $crop->setCroppedMaxSize(2000, 1500); $form = $this->createFormBuilder(['crop' => $crop]) ->add('crop', CropperType::class, [ 'public_url' => '/uploads/photos/'.$comment->getPhotoFilename(), 'aspect_ratio' => 2000 / 1500, ]) ->add('submit', SubmitType::class) ->getForm() ; // Handle the form } } {% block body %} <div> <h4 class="mb-4">Crop your photo</h4> {{ form(form) }} </div> {% endblock %} cropper.html.twig ConferenceController.php
  20. Swup diff --git a/templates/base.html.twig b/templates/base.html.twig index 604ffa7..e97dbf6 100644 --- a/templates/base.html.twig

    +++ b/templates/base.html.twig @@ -11,7 +11,7 @@ {{ encore_entry_link_tags('app') }} {% endblock %} </head> - <body> + <body data-controller="@symfony/ux-swup/swup"> <header class="header"> <h1 class="sr-only"> Conference Guestbook @@ -56,7 +56,7 @@ </nav> </header> - <main role="main" class="container mt-5"> + <main role="main" class="container mt-5" id="swup"> {% block body %}{% endblock %} </main> diff --git a/templates/conference/crop.html.twig b/templates/conference/crop.html.twig index bc6a89d..55f4e91 100644 --- a/templates/conference/crop.html.twig +++ b/templates/conference/crop.html.twig @@ -15,7 +15,7 @@ Crop your photo </h4> - {{ form(form) }} + {{ form(form, {'attr': {'data-swup-form': '1'}}) }} </div> </div> diff --git a/templates/conference/show.html.twig b/templates/conference/show.html.twig index 4cbe628..20f37cd 100644 --- a/templates/conference/show.html.twig +++ b/templates/conference/show.html.twig @@ -67,7 +67,7 @@ Add your own feedback </h3> - {{ form(comment_form) }} + {{ form(comment_form, {'attr': {'data-swup-form': '1'}}) }} </div> </div> </div> show.html.twig crop.html.twig base.html.twig
  21. BlurHash is a compact representation of a placeholder for an

    image LEHV6nWB2yk8pyoJadR*.7kCMdnj https://blurha.sh/ BlurHash?
  22. import { Controller } from 'stimulus'; import { decode }

    from 'blurhash'; export default class extends Controller { connect() { // If blur-hash is defined, use it if (this.element.hasAttribute('data-blur-hash') && !this.element.src) { const pixels = decode(this.element.getAttribute('data-blur-hash'), this.element.width, this.element.height); const canvas = document.createElement('canvas'); canvas.width = this.element.width; canvas.height = this.element.height; const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(this.element.width, this.element.height); imageData.data.set(pixels); ctx.putImageData(imageData, 0, 0); this.element.src = canvas.toDataURL(); } this.element.style.visibility = 'initial'; // Load HD in background const hd = new Image(); // Once loaded, replace image hd.addEventListener('load', () => { this.element.src = this.element.getAttribute('data-hd-src'); }); setTimeout(() => hd.src = this.element.getAttribute('data-hd-src'), 2000); } } <img width="200" height="150" style="visibility: hidden" data-blur-hash="{{ blur_hash(photo_dir~'/t_' ~ comment.photofilename data-controller="lazyimg" data-hd-src="{{ asset('uploads/photos/' ~ comment.photofilename) }}" /> Stimulus Controllers lazyimg_controller.js show.html.twig
  23. Develop JS code in a Symfony app using plain JS,

    
 standard JS tools and packages, 
 thanks to a few conventions and Flex help UX
  24. The key goal is to enable progressive enhancements Standard HTTP

    SEO friendly Accessible ... and still compatible with VueJS/React/... UX