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

Symfony UX

Symfony UX

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