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

Deep dive into the Symfony Debug component

Deep dive into the Symfony Debug component

Thomas Calvet

June 26, 2019
Tweet

More Decks by Thomas Calvet

Other Decks in Programming

Transcript

  1. Deep dive into the
    Symfony Debug
    component

    View full-size slide

  2. Good evening!
    I am Thomas Calvet
    I am a Symfony enthusiast
    I work at ekino
    You can find me on GitHub
    and on the Symfony Devs
    Slack as fancyweb
    2

    View full-size slide

  3. Symfony
    Symfowhat?
    3

    View full-size slide

  4. More than 50 components
    ▪ Stand-alone
    ▪ Decoupled
    ▪ Performant
    ▪ Modern design
    ▪ In permanent evolution
    What is Symfony?
    And a framework
    That is built on top of all
    those components to make
    them work together in an
    efficient and simple way to
    help you create great
    websites faster!
    4

    View full-size slide

  5. Why Symfony?
    ▪ Open source
    ▪ Stable and predictable
    ▪ Best practices
    ▪ Great documentation
    ▪ Big welcoming community
    ▪ Coopetition
    ▪ A vision
    5

    View full-size slide

  6. Do not confuse
    The many debug meanings
    6

    View full-size slide

  7. The Debug component
    The stand-alone component.
    This is what we are going to
    talk about today.
    Do not confuse (1/3)
    The Debug bundle
    The Symfony core bundle
    that integrates the
    stand-alone component into
    the framework.
    7

    View full-size slide

  8. The debug-pack
    The composer meta package
    (symfony/debug-pack) to
    require “debug” related
    packages in one line.
    Do not confuse (2/3)
    The debug flag
    The flag that becomes the
    %kernel.debug% DI parameter
    (from the APP_DEBUG
    environment variable).
    8

    View full-size slide

  9. The “debug” commands
    The console commands to
    help you get information
    about your container, router,
    form types, listeners, etc.
    Do not confuse (3/3)
    9

    View full-size slide

  10. The Debug component
    10

    View full-size slide

  11. “The Debug component
    provides tools to ease
    debugging PHP code.
    11
    It improves the DX!

    View full-size slide

  12. > 125,000,000 installs
    Used by big PHP projects
    Such as Symfony, Laravel, Drupal and API Platform
    The 4th most popular
    Symfony package
    12

    View full-size slide

  13. Three features in three classes
    ExceptionHandler
    Converts a PHP
    exception to a
    pretty and useful
    HTML response
    content.
    ErrorHandler
    Provides a generic
    error handler that
    logs PHP errors
    and that rethrow
    them as
    exceptions.
    DebugClassLoader
    Wraps all the
    registered class
    loaders to do
    checks and
    lightweight static
    analysis on the
    classes they load.
    13

    View full-size slide

  14. PHP exception handling
    101
    14

    View full-size slide

  15. You can throw an exception to interrupt the execution of the code.
    15
    throw new \Exception();
    echo "I love PHP.";
    Fatal error: Uncaught Exception: foo

    View full-size slide

  16. An exception is a normal PHP object.
    16
    Fatal error: Uncaught Exception: foo
    $exception = new \Exception('foo');
    throw $exception;
    echo "I love PHP.";

    View full-size slide

  17. You can catch an exception to handle it.
    17
    I love PHP.
    try {
    throw new \Exception('foo');
    } catch (\Exception $e) {
    }
    echo "I love PHP.";

    View full-size slide

  18. An exception bubbles up indefinitely until it is caught or not.
    18
    I love PHP.
    function foo(): void {
    throw new \Exception('foo');
    }
    try {
    foo();
    } catch (\Exception $e) {
    }
    echo "I love PHP.";

    View full-size slide

  19. You can widen or shrink the types of the exceptions you catch.
    19
    Fatal error: Uncaught Exception: foo
    try {
    throw new \Exception('foo');
    } catch (\LogicException $e) {
    }
    echo "I love PHP.";

    View full-size slide

  20. You can catch several types of exceptions to handle them differently.
    20
    try {
    throw new \DomainException('foo');
    } catch (\LogicException $e) {
    } catch (\DomainException $e) {
    }
    echo "I love PHP.";
    I love PHP.

    View full-size slide

  21. Since PHP 7.1, you can catch several types in one catch block.
    21
    try {
    throw new \DomainException('foo');
    } catch (\LogicException | \DomainException $e) {
    }
    echo "I love PHP.";
    I love PHP.

    View full-size slide

  22. PHP exception handling
    102
    22

    View full-size slide

  23. All core exceptions extends the base \Exception class that implements
    the base \Throwable interface.
    23
    class LogicException extends Exception
    {
    }
    class Exception implements Throwable
    {
    }

    View full-size slide

  24. You can only throw objects that implements the base \Throwable
    interface.
    24
    Fatal error: Uncaught Error: Cannot throw objects that do not
    implement Throwable
    final class Foo
    {
    }
    throw new Foo();

    View full-size slide

  25. And it’s a special interface that cannot be implemented by custom code.
    All your custom exceptions must extends the base \Exception class.
    25
    final class Foo implements \Throwable {
    }
    Fatal error: Class Foo cannot implement interface Throwable,
    extend Exception or Error instead

    View full-size slide

  26. Code in the finally block will be executed after the try and catch blocks.
    26
    try {
    throw new \Exception('foo');
    } catch (\Exception $e) {
    echo 'catch ';
    } finally {
    echo 'finally';
    }
    catch finally

    View full-size slide

  27. Code in the finally block will always be executed after the try and catch
    blocks, whether there is something to catch or not.
    27
    try {
    } catch (\Exception $e) {
    } finally {
    echo 'finally';
    }
    finally

    View full-size slide

  28. Code in the finally block will always be executed even if there is no catch
    block or a return in the try block.
    28
    try {
    return;
    } finally {
    echo 'finally';
    }
    finally

    View full-size slide

  29. You can catch an exception to wrap it.
    29
    namespace App\Service;
    use App\Exception\ProcessException;
    use Vendor\Ccc\Bar;
    final class Foo
    {
    private $bar;
    // __construct method
    public function process(): void
    {
    try {
    $this->bar->init();
    } catch (\RuntimeException $e) {
    throw new ProcessException('Could not process.', 0, $e);
    }
    }
    }

    View full-size slide

  30. You can rethrow an exception.
    30
    namespace App\Service;
    final class Foo
    {
    // $bar property and __construct and clean methods
    public function process(): void
    {
    try {
    $this->bar->init();
    } catch (\RuntimeException $e) {
    $this->clean();
    throw $e;
    }
    }
    }

    View full-size slide

  31. You can define a custom global exception handler.
    31
    set_exception_handler(function (\Throwable $e): void {
    echo 'I love PHP.';
    });
    throw new \Exception('message');
    I love PHP.

    View full-size slide

  32. You can restore the previous global exception handler.
    32
    set_exception_handler(function (\Throwable $e): void {
    echo 'I love PHP.';
    });
    set_exception_handler(function (\Throwable $e): void {
    die();
    });
    restore_exception_handler();
    throw new \Exception('message');
    I love PHP.

    View full-size slide

  33. You can unset the global exception handler by setting it to null.
    33
    Fatal error: Uncaught Exception: message
    set_exception_handler(function (\Throwable $e): void {
    echo 'I love PHP.';
    });
    set_exception_handler(null);
    throw new \Exception('message');

    View full-size slide

  34. The advantages
    Of using exceptions for your domain errors
    34

    View full-size slide

  35. 35
    final class Foo
    {
    /**
    * Returns the number of processed elements
    * or false if an error occurred.
    *
    * @return int|false
    */
    public function process()
    {
    // ...
    }
    }

    View full-size slide

  36. Using exceptions avoid to use mixed return types.
    36
    namespace App\Service;
    use App\Exception\ProcessFailedException;
    final class Foo
    {
    /**
    * Returns the number of processed elements.
    *
    * @throws ProcessFailedException
    */
    public function process(): int
    {
    // ...
    }
    }

    View full-size slide

  37. Using exceptions avoid to use custom hardcoded array structures.
    37
    final class Foo
    {
    public function process(): array
    {
    return [
    'success' => true,
    'data' => '...'
    ];
    }
    public function processssss(): array
    {
    return [
    'error_code' => 'invalid_key',
    ];
    }
    }

    View full-size slide

  38. An exception carries a meaning in its name.
    38
    final class RateLimitExceededException extends \Exception
    {
    }

    View full-size slide

  39. An exception can encapsulate data since it’s a class.
    39
    namespace App\Exception;
    use App\Model\Account;
    final class RateLimitExceededException extends \Exception
    {
    private $account;
    public function __construct(Account $account)
    {
    $this->account = $account;
    }
    public function getAccount(): Acccount
    {
    return $this->account;
    }
    }

    View full-size slide

  40. The Debug ExceptionHandler
    Features
    40

    View full-size slide

  41. “The Debug ExceptionHandler
    converts a PHP exception to a
    pretty and useful HTML
    response content.
    41

    View full-size slide

  42. 42
    function a(string $arg): void { b(time()); }
    function b(int $time): void { c(); }
    function c(): void
    {
    try {
    d();
    } catch (\Exception $e) {
    throw new \RuntimeException('foo', 12, $e);
    }
    }
    function d(): void {
    throw new \DomainException('previous');
    }
    a('arg');

    View full-size slide

  43. The Debug ExceptionHandler
    Internals
    46

    View full-size slide

  44. 47
    final class ExceptionHandler
    {
    public static function register(): void
    {
    set_exception_handler([new self(), 'handle']);
    }
    public function handle(\Exception $exception): void
    {
    $this->sendPhpResponse($exception);
    }
    }
    To enable it : ExceptionHandler::register();

    View full-size slide

  45. 48
    final class ExceptionHandler
    {
    // public static function register() { }
    // public function handle(\Exception $exception) { }
    public function sendPhpResponse(\Exception $exception): void
    {
    header('HTTP/1.0 %s'.$exception->getStatusCode());
    header('Content-Type: text/html');
    echo $this->getContent($exception);
    }
    }

    View full-size slide

  46. 49
    final class ExceptionHandler
    {
    // public static function register() { };
    // public function handle(\Exception $exception) { };
    // public function sendPhpResponse(\Exception $exception) {
    };
    public function getContent(\Exception $exception): string
    {
    // In this method, the $exception variable is processed
    // to build the HTML content.
    // ...
    return $content;
    }
    }

    View full-size slide

  47. To summarize the ExceptionHandler
    ▪ A custom global exception handler is set.
    ▪ The incoming exception is processed to build a
    pretty HTML content.
    ▪ This HTML content is output.
    50

    View full-size slide

  48. The Debug ExceptionHandler
    Zoom on...
    51

    View full-size slide

  49. PHP error handling
    101
    56

    View full-size slide

  50. An error can be triggered by PHP if your code is invalid.
    58
    Warning: Invalid argument supplied for foreach()
    I love PHP.
    foreach (null as $_) {
    }
    echo 'I love PHP.';

    View full-size slide

  51. Some errors does not interrupt the code execution.
    59
    Notice: Undefined variable: foo
    echo $foo;

    View full-size slide

  52. Some errors interrupts the code execution but are recoverable.
    They are thrown as exception since PHP 7.
    60
    Fatal error: Uncaught Error: Call to undefined function foo()
    foo();

    View full-size slide

  53. That means you can catch them to handle them as exceptions.
    61
    I love PHP.
    try {
    foo();
    } catch (\Error $e) {
    echo "I love PHP.";
    }

    View full-size slide

  54. 62
    I love PHP.
    Fatal error: Foo cannot implement ArrayIterator - it is not an
    interface
    Some errors interrupt the code execution and are fatal (they are not
    recoverable).
    echo “I love PHP.”;
    interface Foo extends \ArrayIterator
    {
    }

    View full-size slide

  55. Some errors prevent the code from being compiled.
    The code is never executed.
    63
    Parse error: syntax error, unexpected 'implements'
    (T_IMPLEMENTS), expecting '{'
    echo "I love PHP.";
    interface Foo implements \SeekableIterator
    {
    }

    View full-size slide

  56. PHP error handling
    102
    64

    View full-size slide

  57. All recoverable core errors extends the base \Error class that implements
    the base \Throwable interface.
    65
    class TypeError extends Error
    {
    }
    class Error implements Throwable
    {
    }

    View full-size slide

  58. You can manually trigger an error with a custom message and a
    E_USER_* level.
    66
    Fatal error: foo
    trigger_error('foo', E_USER_ERROR);

    View full-size slide

  59. You can define a custom global error handler.
    67
    I love PHP.
    set_error_handler(function (
    int $errno,
    string $errstr,
    ?string $errfile,
    ?int $errline
    ): bool {
    echo "I love PHP.";
    return true;
    });
    echo $foo;

    View full-size slide

  60. You can restore the previous global error handler.
    68
    I love PHP.
    set_error_handler(function (): bool {
    echo "I love PHP.";
    return true;
    });
    set_error_handler(function (): bool {
    die();
    });
    restore_error_handler();
    echo $foo;

    View full-size slide

  61. If the callback returns false, the normal PHP error handler is still called
    afterwards.
    69
    I love PHP.
    Notice: Undefined variable: foo
    set_error_handler(function (
    int $errno,
    string $errstr,
    ?string $errfile,
    ?int $errline
    ): bool {
    echo "I love PHP.";
    return false;
    });
    echo $foo;

    View full-size slide

  62. You can unset the global error handler by setting it to null.
    70
    set_error_handler(function (
    int $errno,
    string $errstr,
    ?string $errfile,
    ?int $errline
    ): bool {
    echo "I love PHP.";
    return true;
    });
    set_error_handler(null);
    echo $foo;
    Notice: Undefined variable: foo

    View full-size slide

  63. You can register a function that will always be executed on shutdown.
    71
    I love PHP.
    register_shutdown_function(function (): void {
    echo "I love PHP.";
    });

    View full-size slide

  64. The Debug ErrorHandler
    Features
    72

    View full-size slide

  65. “The Debug ErrorHandler
    provides a generic error handler
    that logs PHP errors and that
    rethrow them as exceptions.
    73

    View full-size slide

  66. 74
    echo 1 / 0;

    View full-size slide

  67. 77
    [2019-06-25 20:27:27] logger.CRITICAL: Uncaught Warning: Division by zero
    {"exception":"[object] (ErrorException(code: 0): Warning: Division by zero at
    /Users/thomas.calvet/Fancyweb/talk_debug/index.php:14)"} []

    View full-size slide

  68. The Debug ErrorHandler
    Internals
    79

    View full-size slide

  69. 81
    To enable it : ErrorHandler::register();
    use Psr\Log\LoggerInterface;
    final class ErrorHandler
    {
    private $logger;
    public static function register(): void
    {
    register_shutdown_function(
    __CLASS__.'::handleFatalError'
    );
    set_error_handler([new self(), 'handleError']);
    }
    public function setLogger(LoggerInterface $logger): void
    {
    $this->logger = $logger;
    }
    }

    View full-size slide

  70. 82
    use Symfony\Component\Debug\Exception\FatalErrorException;
    final class ErrorHandler
    {
    private $exceptionHandler;
    // enable and setLogger methods
    public static function handleFatalError(array $error): void
    {
    // ... Get the current exception handler.
    $this->exceptionHandler = $exceptionHandler;
    $this->handleException(
    new FatalErrorException($error['type']/* ... */)
    );
    }
    }

    View full-size slide

  71. 83
    final class ErrorHandler
    {
    private $exceptionHandler;
    private $loggedErrors;
    private $logger;
    // enable, setLogger and handleFatalError methods
    public function handleException(\Throwable $exception)
    {
    if ($this->loggedErrors & $exception->getSeverity()) {
    $this->logger->log(/** ... */);
    }
    $exceptionHandler = $this->exceptionHandler;
    return $exceptionHandler($exception);
    }
    }

    View full-size slide

  72. 84
    final class ErrorHandler
    {
    private $logger;
    private $thrownErrors;
    // enable, setLogger, handleFatalError and
    // handleException methods
    public function handleError(
    int $type
    string $message/** ... */
    ): void
    {
    if ($this->thrownErrors & $type) {
    throw new \ErrorException($message/** ... */);
    }
    $this->logger->log(/** ... */);
    }
    }

    View full-size slide

  73. The Debug ErrorHandler
    Zoom on...
    85

    View full-size slide

  74. Triggering an exception in the magic method __toString() is impossible
    because not returning a string in this method triggers a fatal error.
    86
    Fatal error: Uncaught Error: Method Foo::__toString() must
    return a string value
    final class Foo
    {
    public function __toString()
    {
    @trigger_error('bar', E_USER_ERROR);
    }
    }
    echo new Foo();

    View full-size slide

  75. With the ErrorHandler, the manually triggered error message is visible.
    88
    Fatal error: Method Foo::__toString() must not throw an
    exception, caught ErrorException: User Error: bar
    final class Foo
    {
    public function __toString()
    {
    @trigger_error('bar', E_USER_ERROR);
    }
    }
    echo new Foo();

    View full-size slide

  76. To summarize the ErrorHandler
    ▪ A custom global error handler is set and a
    shutdown function is registered.
    ▪ The global exception handler is proxyfied to be
    able to log.
    ▪ The errors are rethrown as exceptions.
    89

    View full-size slide

  77. PHP class loading
    101
    90

    View full-size slide

  78. Instantiating a class that is unknown will trigger a fatal error.
    91
    new Foo();
    Fatal error: Uncaught Error: Class 'Foo' not found

    View full-size slide

  79. The class needs to be known before.
    92
    final class Foo {
    }
    new Foo();

    View full-size slide

  80. The class can be defined in another included file.
    93
    require 'Foo.php';
    new Foo();

    View full-size slide

  81. Not dynamic
    Everytime you create a new
    class, you need to require it
    manually.
    Isn’t it enough? No!
    Performance overhead
    Required files are parsed and
    compiled into OP codes at
    runtime.
    A request in a web application
    logically never use all the
    available classes, so this is
    wasted memory and time.
    94

    View full-size slide

  82. PHP class loading
    102 - autoload!
    95

    View full-size slide

  83. Warning! Do not use!
    It’s a legacy usage and it has been deprecated since PHP 7.2!
    96
    function __autoload(string $class): void {
    if ('Foo' === $class) {
    require 'Foo.php';
    }
    }
    new Foo();
    The global function __autoload() is called automatically when the code
    references a class or an interface that has not been loaded yet.

    View full-size slide

  84. Because it’s an unique function!
    The __autoload() function is global.
    That means it can be declared only once : first come, first served.
    Consequently, interoperability between vendors cannot exist easily.
    Why it’s still not enough?
    97

    View full-size slide

  85. PHP class loading
    103 - spl_autoload!
    98

    View full-size slide

  86. You can register an autoload function aka a class loader.
    99
    spl_autoload_register(function (string $class): void {
    $file = 'classes/'.$class.'.php';
    if (file_exists($file)) {
    require $file;
    }
    });
    new Foo();

    View full-size slide

  87. You can register an infinite number of autoload functions, thus creating a
    class loader chain.
    100
    spl_autoload_register(function (string $class): void {
    echo "I don't know this class.";
    });
    spl_autoload_register(function (string $class): void {
    final class Foo
    {
    }
    });
    new Foo();

    View full-size slide

  88. You can get all the registered autoload functions.
    101
    spl_autoload_functions();

    View full-size slide

  89. You can unregister an autoload function.
    102
    foreach (spl_autoload_functions() as $function) {
    spl_autoload_unregister($function);
    }

    View full-size slide

  90. PSR-0, PSR-4 and Composer
    PSR-0 (2009)
    Deprecated! A common
    rule to implement in its
    class loader to be
    interoperable with
    other libraries.
    PSR-4 (2013)
    Substituted PSR-0.
    Dropped compatibility
    for PEAR-styles
    classnames and
    allowed to not have the
    whole namespace as a
    directory structure.
    Composer (2012)
    Composer is a tool for
    dependency
    management in PHP.
    It generates an
    autoload.php file that
    uses the
    spl_autoload_*
    functions.
    103

    View full-size slide

  91. The Debug
    DebugClassLoader
    Features
    104

    View full-size slide

  92. “The Debug DebugClassLoader
    wraps all the registered class
    loaders to do checks and
    lightweight static analysis on
    the classes they load.
    105

    View full-size slide

  93. Extending a @final class will trigger a deprecation.
    106
    namespace A;
    /**
    * @final
    */
    class Foo { }
    namespace B;
    use A\Foo;
    final class Bar extends Foo { }

    View full-size slide

  94. 107
    [2019-06-25 20:40:35] logger.INFO: User Deprecated: The "A\A" class is
    considered final. It may change without further notice as of its next major version.
    You should not extend it from "B\B". {"exception":"[object] (ErrorException(code: 0):
    User Deprecated: The \"A\\A\" class is considered final. It may change without
    further notice as of its next major version. You should not extend it from \"B\\B\". at
    /Users/thomas.calvet/Fancyweb/talk_debug/vendor/symfony/debug/DebugClassLo
    ader.php:201)"} []

    View full-size slide

  95. Classes considered as being in the same “vendor” don’t trigger
    deprecations between themselves.
    109
    namespace A;
    /**
    * @final
    */
    class Foo { }
    final class Bar extends Foo { }

    View full-size slide

  96. Extending or implementing a @deprecated or @internal class or interface
    will trigger a deprecation.
    110
    namespace A;
    /**
    * @deprecated since version 2.2
    */
    interface FooInterface { }
    namespace B;
    use A\FooInterface;
    final class Bar implements FooInterface { }

    View full-size slide

  97. Using a @deprecated trait will trigger a deprecation.
    111
    namespace A;
    /**
    * @deprecated
    */
    trait FooTrait { }
    namespace B;
    use A\FooTrait;
    final class Bar {
    use FooTrait;
    }

    View full-size slide

  98. Not implementing an interface virtual method will trigger a deprecation.
    112
    namespace A;
    /**
    * @method int count(string $key)
    */
    interface FooInterface { }
    namespace B;
    use A\FooInterface;
    final class Bar implements FooInterface { }

    View full-size slide

  99. Extending an @internal method will trigger a deprecation.
    113
    namespace A;
    class Foo
    {
    /**
    * @internal
    */
    public function get(): string { }
    }
    namespace B;
    use A\Foo;
    final class Bar extends Foo
    {
    public function get(): string { }
    }

    View full-size slide

  100. Not implementing a virtual argument will trigger a deprecation.
    114
    namespace A;
    class Foo
    {
    /**
    * @param string $arg
    */
    public function get(/* string $arg */): string { }
    }
    namespace B;
    use A\Foo;
    final class Bar extends Foo
    {
    public function get(): string { }
    }

    View full-size slide

  101. The DebugClassLoader also throws exceptions with way better
    messages when there are common autoload problems.
    115
    // Foo.php
    final class Bar
    {
    }
    The autoloader expected class "A\A" to be defined in file
    "/Users/thomas.calvet/Fancyweb/talk_debug/vendor/composer/../.
    ./A.php". The file was found but the class was not in it, the
    class name or namespace probably has a typo.

    View full-size slide

  102. The Debug
    DebugClassLoader
    Internals
    116

    View full-size slide

  103. 117
    final class DebugClassLoader
    {
    private $wrappedClassLoader;
    public function __construct(callable $wrappedClassLoader)
    {
    $this->wrappedClassLoader = $wrappedClassLoader;
    }
    public static function enable(): void
    {
    foreach (spl_autoload_functions() as $function) {
    spl_autoload_unregister($function);
    spl_autoload_register([
    new self($function),
    'loadClass',
    ]);
    }
    }
    }
    To enable it : DebugClassLoader::enable();

    View full-size slide

  104. 118
    final class DebugClassLoader
    {
    private $wrappedClassLoader;
    // __construct and enable methods
    public function loadClass(string $class): void
    {
    $this->wrappedClassLoader->load($class);
    $this->checkClass($class);
    }
    }

    View full-size slide

  105. 119
    final class DebugClassLoader
    {
    // __construct, enable and loadClass methods
    public function checkClass(string $class): void
    {
    $refl = new \ReflectionClass($class);
    // ... Check case mismatch and definition problems.
    $messages = $this->checkAnnotations($refl, $class);
    foreach ($messages as $message) {
    @trigger_error($message, E_USER_DEPRECATED);
    }
    }
    }

    View full-size slide

  106. 120
    final class DebugClassLoader
    {
    // __construct, enable, loadClass and checkClass methods
    public function checkAnnotations(
    \ReflectionClass $refl,
    string $class
    ): array
    {
    $deprecations = [];
    // ... Process and check everything
    return $deprecations;
    }
    }

    View full-size slide

  107. 121
    final class DebugClassLoader
    {
    private static $final = [];
    // __construct, enable, loadClass and checkClass methods
    public function checkAnnotations($refl, $class): array
    {
    // ...
    if (false !== $doc = $refl->getDocComment()) {
    if (preg_match('/@final(.*)/', $doc, $matches) {
    self::$final[$class] = $matches[1];
    }
    }
    // ...
    }
    }

    View full-size slide

  108. 122
    final class DebugClassLoader
    {
    private static $checkedClasses = [];
    // __construct, enable, loadClass and checkClass methods
    public function checkAnnotations($refl, $class): array
    {
    // ...
    if ($parent = get_parent_class($class)) {
    if (!isset(self::$checkedClasses[$parent])) {
    $this->checkClass($parent);
    }
    }
    // ...
    }
    }

    View full-size slide

  109. 123
    final class DebugClassLoader
    {
    // __construct, enable, loadClass and checkClass methods
    public function checkAnnotations($refl, $class): array
    {
    // ...
    if ($parent = get_parent_class($class)) {
    // ...
    if (isset(self::$final[$parent])) {
    $deprecations[] = sprintf('
    The "%s" class is considered final.
    You should not extend it from "%s".',
    $parent, $class
    );
    }
    }
    // ...
    }
    }

    View full-size slide

  110. The Debug
    DebugClassLoader
    Zoom on...
    124

    View full-size slide

  111. 125
    // #\n \*
    @method\s+(static\s+)?+(?:[\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\
    )]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#
    // #\n\s+\* @param +((?(?!callable *\().*?|callable
    *\(.*\).*?))(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#
    Real DebugClassLoader regexes.

    View full-size slide

  112. 126
    $doc = $refl->getDocComment();
    if (
    false !== strpos($doc, '@method') &&
    preg_match_all('/@method(.*)/')
    ) {
    }
    The DebugClassLoader must be fast, because it does its analysis at
    runtime. This why there are micro optimisations.

    View full-size slide

  113. To summarize the DebugClassLoader
    ▪ It wraps all the defined class loaders.
    ▪ It loads the classes by calling the wrapped class
    loaders.
    ▪ It does checks and lightweight static analysis to
    trigger useful deprecations.
    127

    View full-size slide

  114. Upcoming in Symfony 5!
    128

    View full-size slide

  115. Namespaces mapping
    https:/
    /github.com/symfony/sy
    mfony/pull/30899
    Control the “same vendor”
    check to force the exposition or
    mute deprecations.
    New DebugClassLoader features
    Strict return types
    https:/
    /github.com/symfony/symf
    ony/pull/30323
    Throw deprecations when classes
    methods can already use strict
    return types to prepare for future
    major versions of their parents /
    implemented interfaces.
    129

    View full-size slide

  116. New ErrorHandler component
    ▪ https:/
    /github.com/symfony/symfony/pull/310
    65 by Yonel Ceruto (@yceruto)
    ▪ Extract the error handler mechanism (from the
    ExceptionHandler and the ErrorHandler) to a new
    component.
    ▪ The goal : be able to render the errors in many
    formats (HTML, JSON, XML, etc.)
    ▪ You can also add your own error renderers!
    130

    View full-size slide

  117. Thanks!
    Any questions?
    You can find me at:
    fancyweb on GitHub and on the Symfony Devs Slack
    [email protected] by mail
    131

    View full-size slide