▪ 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
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
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
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
differently. 20 try { throw new \DomainException('foo'); } catch (\LogicException $e) { } catch (\DomainException $e) { } echo "I love PHP."; I love PHP.
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
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); } } }
App\Service; use App\Exception\ProcessFailedException; final class Foo { /** * Returns the number of processed elements. * * @throws ProcessFailedException */ public function process(): int { // ... } }
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; } }
{ set_exception_handler([new self(), 'handle']); } public function handle(\Exception $exception): void { $this->sendPhpResponse($exception); } } To enable it : ExceptionHandler::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; } }
- 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 { }
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;
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']/* ... */) ); } }
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();
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();
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
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
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.
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
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();
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
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)"} []
will trigger a deprecation. 110 namespace A; /** * @deprecated since version 2.2 */ interface FooInterface { } namespace B; use A\FooInterface; final class Bar implements FooInterface { }
112 namespace A; /** * @method int count(string $key) */ interface FooInterface { } namespace B; use A\FooInterface; final class Bar implements FooInterface { }
A; class Foo { /** * @internal */ public function get(): string { } } namespace B; use A\Foo; final class Bar extends Foo { public function get(): string { } }
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 { } }
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.
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 ); } } // ... } }
&& preg_match_all('/@method(.*)/') ) { } The DebugClassLoader must be fast, because it does its analysis at runtime. This why there are micro optimisations.
class loaders. ▪ It loads the classes by calling the wrapped class loaders. ▪ It does checks and lightweight static analysis to trigger useful deprecations. 127
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
(@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