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. Bridging Communities
    Fabien Potencier
    @fabpot

    View Slide

  2. Bridging Communities
    Fabien Potencier
    @fabpot

    View Slide

  3. View Slide

  4. 1. The Community


    2. HttpKernelInterface


    3. Symfony Flex

    View Slide

  5. A Strong Community


    A growing core team - 17 members


    Amazing support team - many


    Dedicated doc team - 7 members


    A growing community of developers - tons

    View Slide

  6. 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);


    }

    View Slide

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

    View Slide

  8. RAD?

    View Slide

  9. 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

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

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

    View Slide

  13. Security


    Bootstrap a new project the fast way
    $ symfony console make:migration


    $ symfony console doctrine:migrations:migrate -n

    View Slide

  14. https://127.0.0.1:8000/register
    $ symfony open:local:webmail

    View Slide

  15. https://127.0.0.1:8000/logout
    https://127.0.0.1:8000/login

    View Slide

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

    View Slide

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

    View Slide

  18. What's next?


    Embrace the larger ecosystem even more

    View Slide

  19. View Slide

  20. http://prototypejs.org/ - https://symfony.com/legacy/doc/tutorial/1_0/en/symfony-ajax

    View Slide

  21. ??

    View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. UX

    View Slide

  26. composer req

    symfony/ux-dropzone

    symfony/ux-cropper

    symfony/ux-lazyimg

    symfony/ux-swup

    View Slide

  27. 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

    View Slide

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

    View Slide

  29. yarn && yarn encore dev

    View Slide

  30. {


    - "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

    View Slide

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

    View Slide

  32. 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

    View Slide

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

    View Slide

  34. 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 %}





    Crop your photo


    {{ form(form) }}





    {% endblock %}
    cropper.html.twig
    ConferenceController.php

    View Slide

  35. Like a Native App?
    AJAX requests
    Browser History
    "Static" area
    Transition
    between "pages"

    View Slide

  36. 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 %}





    -


    +








    Conference Guestbook


    @@ -56,7 +56,7 @@









    -


    +


    {% block body %}{% endblock %}



    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






    - {{ form(form) }}


    + {{ form(form, {'attr': {'data-swup-form': '1'}}) }}







    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






    - {{ form(comment_form) }}


    + {{ form(comment_form, {'attr': {'data-swup-form': '1'}}) }}









    show.html.twig
    crop.html.twig
    base.html.twig

    View Slide

  37. Noticed how images are loaded?

    View Slide

  38. BlurHash is a compact representation of a placeholder for an image
    LEHV6nWB2yk8pyoJadR*.7kCMdnj
    https://blurha.sh/
    BlurHash?

    View Slide

  39. 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);


    }


    }


    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

    View Slide

  40. View Slide

  41. Develop JS code in a Symfony app


    using plain JS,

    standard JS tools and packages,

    thanks to a few conventions


    and Flex help
    UX

    View Slide

  42. The key goal is to enable progressive enhancements


    Standard HTTP


    SEO friendly


    Accessible


    ... and still compatible with VueJS/React/...
    UX

    View Slide

  43. Titouan Galopin

    is joining the Symfony Core Team


    Leading the Symfony UX initiative

    View Slide

  44. https://symfony.com/sponsor
    Sponsor Symfony
    Thank you!

    View Slide