diff --git a/.appveyor.yml b/.appveyor.yml index 8993433c553cc..dbf83731eaffb 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,18 +10,18 @@ init: - SET PATH=c:\php;%PATH% - SET COMPOSER_NO_INTERACTION=1 - SET SYMFONY_DEPRECATIONS_HELPER=max[indirect]=7 - - SET "SYMFONY_REQUIRE=>=4.2" + - SET "SYMFONY_REQUIRE=>=4.4" - SET ANSICON=121x90 (121x90) - SET SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE=1 - REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v DelayedExpansion /t REG_DWORD /d 1 /f install: - mkdir c:\php && cd c:\php - - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-7.1.3-Win32-VC14-x86.zip - - 7z x php-7.1.3-Win32-VC14-x86.zip -y >nul + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-7.2.5-Win32-VC15-x86.zip + - 7z x php-7.2.5-Win32-VC15-x86.zip -y >nul - cd ext - - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_apcu-5.1.8-7.1-ts-vc14-x86.zip - - 7z x php_apcu-5.1.8-7.1-ts-vc14-x86.zip -y >nul + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_apcu-5.1.17-7.2-ts-vc15-x86.zip + - 7z x php_apcu-5.1.17-7.2-ts-vc15-x86.zip -y >nul - cd .. - copy /Y php.ini-development php.ini-min - echo memory_limit=-1 >> php.ini-min @@ -59,7 +59,10 @@ test_script: - SET SYMFONY_PHPUNIT_SKIPPED_TESTS=phpunit.skipped - copy /Y c:\php\php.ini-min c:\php\php.ini - IF %APPVEYOR_REPO_BRANCH% neq master (rm -Rf src\Symfony\Bridge\PhpUnit) + - mv src\Symfony\Component\HttpClient\phpunit.xml.dist src\Symfony\Component\HttpClient\phpunit.xml - php phpunit src\Symfony --exclude-group tty,benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony\Component\HttpClient || SET X=!errorlevel! - copy /Y c:\php\php.ini-max c:\php\php.ini - php phpunit src\Symfony --exclude-group tty,benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony\Component\HttpClient || SET X=!errorlevel! - exit %X% diff --git a/.github/ISSUE_TEMPLATE/1_Bug_report.md b/.github/ISSUE_TEMPLATE/1_Bug_report.md index 4a64e16edf0a5..0e34075718894 100644 --- a/.github/ISSUE_TEMPLATE/1_Bug_report.md +++ b/.github/ISSUE_TEMPLATE/1_Bug_report.md @@ -1,6 +1,7 @@ --- name: 🐛 Bug Report about: Report errors and problems +labels: Bug --- diff --git a/.github/patch-types.php b/.github/patch-types.php index 2df0774bec4c6..d9b1ed98f2bfe 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -7,15 +7,6 @@ require __DIR__.'/../.phpunit/phpunit-8.3-0/vendor/autoload.php'; -file_put_contents(__DIR__.'/../vendor/autoload.php', preg_replace('/^return (Composer.*);/m', <<<'EOTXT' -$loader = \1; -$loader->addClassMap(['Symfony\Component\Debug\Exception\FlattenException' => \dirname(__DIR__).'/src/Symfony/Component/Debug/Exception/FlattenException.php']); - -return $loader; - -EOTXT -, file_get_contents(__DIR__.'/../vendor/autoload.php'))); - $loader = require __DIR__.'/../vendor/autoload.php'; Symfony\Component\ErrorHandler\DebugClassLoader::enable(); diff --git a/.php_cs.dist b/.php_cs.dist index e492bd6cd64db..3f0e86f4c38d8 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -39,7 +39,6 @@ return PhpCsFixer\Config::create() // test template ->notPath('Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php') // explicit trigger_error tests - ->notPath('Symfony/Component/Debug/Tests/DebugClassLoaderTest.php') ->notPath('Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php') ) ; diff --git a/.travis.yml b/.travis.yml index a5246b3eafb58..c894464d541a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,16 +17,17 @@ addons: env: global: - - MIN_PHP=7.1.3 + - MIN_PHP=7.2.5 - SYMFONY_PROCESS_PHP_TEST_BINARY=~/.phpenv/shims/php - MESSENGER_AMQP_DSN=amqp://localhost/%2f/messages - MESSENGER_REDIS_DSN=redis://127.0.0.1:7006/messages + - MESSENGER_SQS_DSN=sqs://localhost:9494/messages?sslmode=disable - SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE=1 matrix: include: - - php: 7.1 - env: php_extra="7.2 7.4" + - php: 7.2 + env: php_extra="7.4" - php: 7.3 env: deps=high - php: 7.4 @@ -48,13 +49,20 @@ services: before_install: - | - # Enable Sury ppa + # Enable extra ppa sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 6B05F25D762E3157 sudo add-apt-repository -y ppa:ondrej/php sudo rm /etc/apt/sources.list.d/google-chrome.list sudo rm /etc/apt/sources.list.d/mongodb-3.4.list + sudo wget -O - http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add - + echo "deb http://packages.couchbase.com/ubuntu xenial xenial/main" | sudo tee /etc/apt/sources.list.d/couchbase.list sudo apt update - sudo apt install -y librabbitmq-dev libsodium-dev + sudo apt install -y librabbitmq-dev libsodium-dev libcouchbase-dev zlib1g-dev + + - | + # Start Couchbase + docker pull couchbase:6.0.1 + docker run -d --name couchbase -p 8091-8094:8091-8094 -p 11210:11210 couchbase:6.0.1 - | # Start Redis cluster @@ -62,6 +70,30 @@ before_install: docker run -d -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 -p 7006:7006 -p 7007:7007 -e "STANDALONE=true" --name redis-cluster grokzen/redis-cluster:5.0.4 export REDIS_CLUSTER_HOSTS='localhost:7000 localhost:7001 localhost:7002 localhost:7003 localhost:7004 localhost:7005' + - | + # Start Sqs server + docker pull feathj/fake-sqs + docker run -d -p 9494:9494 --name sqs feathj/fake-sqs + + - | + # Start Kafka and install an up-to-date librdkafka + docker network create kafka_network + docker pull wurstmeister/zookeeper:3.4.6 + docker run -d --network kafka_network --name zookeeper wurstmeister/zookeeper:3.4.6 + docker pull wurstmeister/kafka:2.12-2.3.1 + docker run -d -p 9092:9092 --network kafka_network -e "KAFKA_AUTO_CREATE_TOPICS_ENABLE=false" -e "KAFKA_CREATE_TOPICS=test-topic:1:1:compact" -e "KAFKA_ADVERTISED_HOST_NAME=kafka" -e "KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181" -e "KAFKA_ADVERTISED_PORT=9092" --name kafka wurstmeister/kafka:2.12-2.3.1 + export KAFKA_BROKER=kafka:9092 + sudo sh -c 'echo "\n127.0.0.1 kafka\n" >> /etc/hosts' + + mkdir /tmp/librdkafka + curl https://codeload.github.com/edenhill/librdkafka/tar.gz/v0.11.6 | tar xzf - -C /tmp/librdkafka + (cd /tmp/librdkafka/librdkafka-0.11.6 && ./configure && make && sudo make install) + + - | + # Create new Couchbase Cluster and Bucket ephemeral + docker exec couchbase /opt/couchbase/bin/couchbase-cli cluster-init -c localhost:8091 --cluster-username=Administrator --cluster-password=111111 --cluster-ramsize=256 + docker exec couchbase /opt/couchbase/bin/couchbase-cli bucket-create -c localhost:8091 --bucket=cache --bucket-type=ephemeral --bucket-ramsize=100 -u Administrator -p 111111 + - | # General configuration set -e @@ -175,7 +207,9 @@ before_install: tfold ext.igbinary tpecl igbinary-3.1.2 igbinary.so $INI tfold ext.zookeeper tpecl zookeeper-0.7.1 zookeeper.so $INI tfold ext.amqp tpecl amqp-1.9.4 amqp.so $INI + tfold ext.rdkafka tpecl rdkafka-4.0.2 rdkafka.so $INI tfold ext.redis tpecl redis-4.3.0 redis.so $INI "no" + tfold ext.couchbase tpecl couchbase-2.6.0 couchbase.so $INI done - | # List all php extensions with versions @@ -213,7 +247,6 @@ install: if [[ ! $deps ]]; then php .github/build-packages.php HEAD^ src/Symfony/Bridge/PhpUnit src/Symfony/Contracts - composer remove --dev --no-update paragonie/sodium_compat else export SYMFONY_DEPRECATIONS_HELPER=weak && cp composer.json composer.json.orig && @@ -248,7 +281,7 @@ install: - | # Install symfony/flex if [[ $deps = low ]]; then - export SYMFONY_REQUIRE='>=2.3' + export SYMFONY_REQUIRE='>=3.4' else export SYMFONY_REQUIRE=">=$SYMFONY_VERSION" fi diff --git a/CHANGELOG-5.0.md b/CHANGELOG-5.0.md new file mode 100644 index 0000000000000..abc3adf3e1e74 --- /dev/null +++ b/CHANGELOG-5.0.md @@ -0,0 +1,603 @@ +CHANGELOG for 5.0.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 5.0 minor versions. + +To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash +To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.0.0...v5.0.1 + +* 5.0.5 (2020-02-29) + + * bug #35781 [Form] NumberToLocalizedStringTransformer return int if scale = 0 (VincentLanglet) + * bug #35846 [Serializer] prevent method calls on null values (xabbuh) + * bug #35897 [FrameworkBundle] add missing Messenger options to XML schema definition (xabbuh) + * bug #35870 [ErrorHandler] fix parsing static return type on interface method annotation (alekitto) + * bug #35839 [Security] Allow switching to another user when already switched (chalasr) + * bug #35851 [DoctrineBridge] Use new Types::* constants and support new json types (fancyweb) + * bug #35841 [Notifier] Dispatch message event in null transport (jschaedl) + * bug #35716 [PhpUnitBridge] Fix compatibility to PHPUnit 9 (Benjamin) + * bug #35803 [Cache] Fix versioned namespace atomic clears (trvrnrth) + * bug #35817 [DoctrineBridge] Use new Types::* constants and support new json type (fancyweb) + * bug #35832 [Debug][ErrorHandler] improved deprecation notices for methods new args and return type (HeahDude) + * bug #35827 [BrowserKit] Nested file array prevents uploading file (afilina) + * bug #35826 [Notifier] Add correct tags for NullTransportFactory (jschaedl) + * bug #35830 [FrameworkBundle] Skip notifiers tags in UnusedTagsPass (chalasr) + * bug #35707 [ExpressionLanguage] Fixed collisions of character operators with object properties (Andrej-in-ua) + * bug #35794 [DoctrineBridge][DoctrineExtractor] Fix indexBy with custom and some core types (fancyweb) + * bug #35787 [PhpUnitBridge] Use trait instead of extending deprecated class (marcello-moenkemeyer) + * bug #35792 [Security] Prevent TypeError in case RememberMetoken has no attached user (nikophil) + * bug #35735 [Routing] Add locale requirement for localized routes (mtarld) + * bug #35772 [Config] don't throw on missing excluded paths (nicolas-grekas) + * bug #35774 [Ldap] force default network timeout (nicolas-grekas) + * bug #35702 [VarDumper] fixed DateCaster not displaying additional fields (Makdessi Alex) + * bug #35722 [HttpKernel] Set previous exception when rethrown from controller resolver (danut007ro) + * bug #35714 [HttpClient] Correctly remove trace level options for HttpCache (aschempp) + * bug #35718 [HttpKernel] fix registering DebugHandlersListener regardless of the PHP_SAPI (nicolas-grekas) + * bug #35728 Add missing autoload calls (greg0ire) + * bug #35693 [Finder] Fix unix root dir issue (chr-hertel) + * bug #35709 [HttpFoundation] fix not sending Content-Type header for 204 responses (Tobion) + * bug #35710 [ErrorHandler] silence warning when zend.assertions=-1 (nicolas-grekas) + * bug #35676 [Console] Handle zero row count in appendRow() for Table (Adam Prickett) + * bug #35696 [Console] Don't load same-namespace alternatives on exact match (chalasr) + * bug #35674 [HttpClient] fix getting response content after its destructor throwed an HttpExceptionInterface (nicolas-grekas) + * bug #35672 [HttpClient] fix HttpClientDataCollector when handling canceled responses (thematchless) + * bug #35641 [Process] throw when PhpProcess::fromShellCommandLine() is used (Guikingone) + * bug #35645 [ErrorHandler] Never throw on warnings triggered by assert() and set assert.exception=1 in Debug::enable() (nicolas-grekas) + * bug #35633 [Mailer] Do not ping the SMTP server before sending every message (micheh) + * bug #33897 [Console] Consider STDIN interactive (ostrolucky) + * bug #35605 [HttpFoundation][FrameworkBundle] fix support for samesite in session cookies (fabpot) + * bug #35609 [DoctrineBridge] Fixed submitting ids with query limit or offset (HeahDude) + * bug #35616 [Workflow] Make method signature compatible with 4.4 (pbowyer) + * bug #35597 [PHPunit bridge] Provide current file as file path (greg0ire) + * bug #33960 [DI] Unknown env prefix not recognized as such (ro0NL) + * bug #35342 [DI] Fix support for multiple tags for locators and iterators (Alexandre Parent) + * bug #33820 [PhpUnitBridge] Fix some errors when using serialized deprecations (l-vo) + * bug #35553 Fix HTTP client config handling (julienfalque) + * bug #35588 [ErrorHandler] Escape variable in Exception template (jderusse) + * bug #35583 Add missing use statements (fabpot) + * bug #35582 Missing use statement 4.4 (fabpot) + * bug #34123 [Form] Fix handling of empty_data's \Closure value in Date/Time form types (yceruto) + * bug #35537 [Config][XmlReferenceDumper] Prevent potential \TypeError (fancyweb) + * bug #35227 [Mailer] Fix broken mandrill http send for recipients with names (vilius-g) + * bug #35430 [Translation] prefer intl domain when adding messages to catalogue (Guite) + * bug #35497 Fail on empty password verification (without warning on any implementation) (Stefan Kruppa) + * bug #35546 [Validator] check for __get method existence if property is uninitialized (alekitto) + * bug #35332 [Yaml][Inline] Fail properly on empty object tag and empty const tag (fancyweb) + * bug #35489 [PhpUnitBridge] Fix running skipped tests expecting only deprecations (chalasr) + * bug #35161 [FrameworkBundle] Check non-null type for numeric type (Arman-Hosseini) + * bug #34059 [DomCrawler] Skip disabled fields processing in Form (sbogx) + * bug #34114 [Console] SymonfyStyle - Check value isset to avoid PHP notice (leevigraham) + * bug #35557 [Config] dont catch instances of Error (nicolas-grekas) + * bug #35562 [HttpClient] fix HttpClientDataCollector when handling canceled responses (nicolas-grekas) + +* 5.0.4 (2020-01-31) + + * bug #35530 [HttpClient] Fix regex bearer (noniagriconomie) + * bug #35532 [Validator] fix access to uninitialized property when getting value (greedyivan) + * bug #35486 [Translator] Default value for 'sort' option in translation:update should be 'asc' (versgui) + * bug #35305 [HttpKernel] Fix stale-if-error behavior, add tests (mpdude) + * bug #34808 [PhpUnitBridge] Properly handle phpunit arguments for configuration file (biozshock) + * bug #35517 [Intl] Provide more locale translations (ro0NL) + * bug #35518 [Mailer] Fix STARTTLS support for Postmark and Mandrill (fabpot) + * bug #35480 [Messenger] Check for all serialization exceptions during message dec… (Patrick Berenschot) + * bug #35502 [Messenger] Fix bug when using single route with XML config (Nyholm) + * bug #35438 [SecurityBundle] fix ldap_bind service arguments (Ioni14) + * bug #35429 [DI] CheckTypeDeclarationsPass now checks if value is type of parameter type (pfazzi) + * bug #35464 [ErrorHandler] Add debug argument to decide whether debug page is shown or not (yceruto) + * bug #35423 Fixes a runtime error when accessing the cache panel (DamienHarper) + * bug #35428 [Cache] fix checking for igbinary availability (nicolas-grekas) + * bug #35424 [HttpKernel] Check if lock can be released (sjadema) + +* 5.0.3 (2020-01-21) + + * bug #35364 [Yaml] Throw on unquoted exclamation mark (fancyweb) + * bug #35065 [Security] Use supportsClass in addition to UnsupportedUserException (linaori) + * bug #35351 Revert #34797 "Fixed translations file dumper behavior" and fix #34713 (yceruto) + * bug #35356 [Filesystem] chown and chgrp should also accept int as owner and group (Slamdunk) + * bug #35335 [Security] Fix RememberMe with null password (jderusse) + * bug #35339 [String] add missing encoding when calling mb_ord() (nicolas-grekas) + * bug #35355 [DI] Fix EnvVar not loaded when Loader requires an env var (jderusse) + * bug #35343 [Security] Fix RememberMe with null password (jderusse) + * bug #34223 [DI] Suggest typed argument when binding fails with untyped argument (gudfar) + * bug #35323 [FrameworkBundle] Set booted flag to false when test kernel is unset (thiagocordeiro) + * bug #35324 [HttpClient] Fix strict parsing of response status codes (Armando-Walmeric) + * bug #35318 [Yaml] fix PHP const mapping keys using the inline notation (xabbuh) + * bug #35306 [FrameworkBundle] Make sure one can use fragments.hinclude_default_template (Nyholm) + * bug #35304 [HttpKernel] Fix that no-cache MUST revalidate with the origin (mpdude) + * bug #35299 Avoid `stale-if-error` in FrameworkBundle's HttpCache if kernel.debug = true (mpdude) + * bug #35240 [SecurityBundle] Fix collecting traceable listeners info on lazy firewalls (chalasr) + * bug #35151 [DI] deferred exceptions in ResolveParameterPlaceHoldersPass (Islam93) + * bug #35290 [Filesystem][FilesystemCommonTrait] Use a dedicated directory when there are no namespace (fancyweb) + * bug #35099 [FrameworkBundle] Do not throw exception on value generate key (jderusse) + * bug #35278 [EventDispatcher] expand listener in place (xabbuh) + * bug #35269 [HttpKernel][FileLocator] Fix deprecation message (fancyweb) + * bug #35254 [PHPUnit-Bridge] Fail-fast in simple-phpunit if one of the passthru() commands fails (mpdude) + * bug #35261 [Routing] Fix using a custom matcher & generator dumper class (fancyweb) + * bug #34643 [Dotenv] Fixed infinite loop with missing quote followed by quoted value (naitsirch) + * bug #35239 [Security\Http] Prevent canceled remember-me cookie from being accepted (chalasr) + * bug #35267 [Debug] fix ClassNotFoundFatalErrorHandler (nicolas-grekas) + * bug #35252 [Serializer] Fix cache in MetadataAwareNameConverter (bastnic) + * bug #35200 [TwigBridge] do not render preferred choices as selected (xabbuh) + * bug #35243 [HttpKernel] release lock explicitly (nicolas-grekas) + * bug #35193 [TwigBridge] button_widget now has its title attr translated even if its label = null or false (stephen-lewis) + * bug #35219 [PhpUnitBridge] When using phpenv + phpenv-composer plugin, composer executable is wrapped into a bash script (oleg-andreyev) + * bug #35150 [Messenger] Added check if json_encode succeeded (toooni) + * bug #35137 [Messenger] Added check if json_encode succeeded (toooni) + * bug #35170 [FrameworkBundle][TranslationUpdateCommand] Do not output positive feedback on stderr (fancyweb) + * bug #35245 [HttpClient] fix exception in case of PSR17 discovery failure (nicolas-grekas) + * bug #35244 [Cache] fix processing chain adapter based cache pool (xabbuh) + * bug #35247 [FrameworkBundle][ContainerLintCommand] Only skip .errored. services (fancyweb) + * bug #35225 [DependencyInjection] Handle ServiceClosureArgument for callable in container linting (shieldo) + * bug #35223 [HttpClient] Don't read from the network faster than the CPU can deal with (nicolas-grekas) + * bug #35214 [DI] DecoratorServicePass should keep container.service_locator on the decorated definition (malarzm) + * bug #35209 [HttpClient] fix support for non-blocking resource streams (nicolas-grekas) + * bug #35210 [HttpClient] NativeHttpClient should not send >1.1 protocol version (nicolas-grekas) + * bug #35162 [Mailer] Make sure you can pass custom headers to Mailgun (Nyholm) + * bug #33672 [Mailer] Remove line breaks in email attachment content (Stuart Fyfe) + * bug #35101 [Routing] Fix i18n routing when the url contains the locale (fancyweb) + * bug #35124 [TwigBridge][Form] Added missing help messages in form themes (cmen) + * bug #35195 [HttpClient] fix casting responses to PHP streams (nicolas-grekas) + * bug #35168 [HttpClient] fix capturing SSL certificates with NativeHttpClient (nicolas-grekas) + * bug #35134 [PropertyInfo] Fix BC issue in phpDoc Reflection library (jaapio) + * bug #35184 [Mailer] Payload sent to Sendgrid doesn't include names (versgui) + * bug #35173 [Mailer][MailchimpBridge] Fix missing attachments when sending via Mandrill API (vilius-g) + * bug #35172 [Mailer][MailchimpBridge] Fix incorrect sender address when sender has name (vilius-g) + * bug #35125 [Translator] fix performance issue in MessageCatalogue and catalogue operations (ArtemBrovko) + * bug #35120 [HttpClient] fix scheduling pending NativeResponse (nicolas-grekas) + * bug #35117 [Cache] do not overwrite variable value (xabbuh) + * bug #35113 [VarDumper] Fix "Undefined index: argv" when using CliContextProvider (xepozz) + * bug #34673 Migrate server:log command away from WebServerBundle (jderusse) + * bug #35103 [Translation] Use `locale_parse` for computing fallback locales (alanpoulain) + * bug #35060 [Security] Fix missing defaults for auto-migrating encoders (chalasr) + * bug #35067 [DependencyInjection][CheckTypeDeclarationsPass] Handle \Closure for callable (fancyweb) + * bug #35094 [Console] Fix filtering out identical alternatives when there is a command loader (fancyweb) + +* 5.0.2 (2019-12-19) + + * bug #35051 [DependencyInjection] Fix binding tagged services to containers (nicolas-grekas) + * bug #35039 [DI] skip looking for config class when the extension class is anonymous (nicolas-grekas) + * bug #35049 [ProxyManager] fix generating proxies for root-namespaced classes (nicolas-grekas) + * bug #35022 [Dotenv] FIX missing getenv (mccullagh) + * bug #35023 [HttpKernel] ignore failures generated by opcache.restrict_api (nicolas-grekas) + * bug #35024 [HttpFoundation] fix pdo session handler for sqlsrv (azjezz) + * bug #35025 [HttpClient][Psr18Client] Remove Psr18ExceptionTrait (fancyweb) + * bug #35028 [TwigBridge] Fix invalid typehint for subject in is_granted Twig function (emodric) + * bug #35015 [Config] fix perf of glob discovery when GLOB_BRACE is not available (nicolas-grekas) + * bug #35014 [HttpClient] make pushed responses retry-able (nicolas-grekas) + * bug #35010 [VarDumper] ignore failing __debugInfo() (nicolas-grekas) + * bug #34998 [DI] fix auto-binding service providers to their service subscribers (nicolas-grekas) + * bug #34954 [Mailer] Fixed undefined index when sending via Mandrill API (wulff) + * bug #33670 [DI] Service locators can't be decorated (malarzm) + * bug #35000 [Console][SymfonyQuestionHelper] Handle multibytes question choices keys and custom prompt (fancyweb) + * bug #35005 [HttpClient] force HTTP/1.1 when NTLM auth is used (nicolas-grekas) + * bug #34707 [Validation][FrameworkBundle] Allow EnableAutoMapping to work without auto-mapping namespaces (ogizanagi) + * bug #34996 Fix displaying anonymous classes on PHP 7.4 (nicolas-grekas) + * bug #29839 [Validator] fix comparisons with null values at property paths (xabbuh) + * bug #34900 [DoctrineBridge] Fixed submitting invalid ids when using queries with limit (HeahDude) + * bug #34791 [Serializer] Skip uninitialized (PHP 7.4) properties in PropertyNormalizer and ObjectNormalizer (vudaltsov) + * bug #34956 [Messenger][AMQP] Use delivery_mode=2 by default (lyrixx) + * bug #34915 [FrameworkBundle] Fix invalid Windows path normalization in TemplateNameParser (mvorisek) + * bug #34981 stop using deprecated Doctrine persistence classes (xabbuh) + * bug #34904 [Validator][ConstraintValidator] Safe fail on invalid timezones (fancyweb) + * bug #34935 [FrameworkBundle][DependencyInjection] Skip removed ids in the lint container command and its associated pass (fancyweb) + * bug #34957 [Security] Revert "AbstractAuthenticationListener.php error instead info" (larzuk91) + * bug #34922 [FrameworkBundle][Secrets] Hook configured local dotenv file (fancyweb) + * bug #34967 [HttpFoundation] fix redis multi host dsn not recognized (Jan Christoph Beyer) + * bug #34963 [Lock] fix constructor argument type declaration (xabbuh) + * bug #34955 Require doctrine/persistence ^1.3 (nicolas-grekas) + * bug #34923 [DI] Fix support for immutable setters in CallTrait (Lctrs) + * bug #34878 [TwigBundle] fix broken FilesystemLoader::exists() with Twig 3 (dpesch) + * bug #34921 [HttpFoundation] Removed "Content-Type" from the preferred format guessing mechanism (yceruto) + * bug #34886 [HttpKernel] fix triggering deprecation in file locator (xabbuh) + * bug #34918 [Translation] fix memoryleak in PhpFileLoader (nicolas-grekas) + * bug #34920 [Routing] fix memoryleak when loading compiled routes (nicolas-grekas) + * bug #34787 [Cache] Propagate expiry when syncing items in ChainAdapter (trvrnrth) + * bug #34694 [Validator] Fix auto-mapping constraints should not be validated (ogizanagi) + * bug #34848 [Process] change the syntax of portable command lines (nicolas-grekas) + * bug #34862 [FrameworkBundle][ContainerLintCommand] Reinitialize bundles when the container is reprepared (fancyweb) + * bug #34896 [Cache] fix memory leak when using PhpFilesAdapter (nicolas-grekas) + * bug #34438 [HttpFoundation] Use `Cache-Control: must-revalidate` only if explicit lifetime has been given (mpdude) + * bug #34449 [Yaml] Implement multiline string as scalar block for tagged values (natepage) + * bug #34601 [MonologBridge] Fix debug processor datetime type (mRoca) + * bug #34842 [ExpressionLanguage] Process division by zero (tigr1991) + * bug #34902 [PropertyAccess] forward caught exception (xabbuh) + * bug #34903 Fixing bad order of operations with null coalescing operator (weaverryan) + * bug #34888 [TwigBundle] add tags before processing them (xabbuh) + * bug #34760 [Mailer] Fix SMTP Authentication when using STARTTLS (DjLeChuck) + * bug #34762 [Config] never try loading failed classes twice with ClassExistenceResource (nicolas-grekas) + * bug #34783 [DependencyInjection] Handle env var placeholders in CheckTypeDeclarationsPass (fancyweb) + * bug #34839 [Cache] fix memory leak when using PhpArrayAdapter (nicolas-grekas) + * bug #34801 [String] implement __sleep()/__wakeup() on strings (nicolas-grekas) + * bug #34782 [String] inline Latin-ASCII rules (nicolas-grekas) + * bug #34812 [Yaml] fix parsing negative octal numbers (xabbuh) + * bug #34854 [Messenger] gracefully handle missing event dispatchers (xabbuh) + * bug #34802 [Security] Check UserInterface::getPassword is not null before calling needsRehash (dbrekelmans) + * bug #34788 [SecurityBundle] Properly escape regex in AddSessionDomainConstraintPass (fancyweb) + * bug #34859 [SecurityBundle] Fix TokenStorage::reset not called in stateless firewall (jderusse) + * bug #34827 [HttpFoundation] get currently session.gc_maxlifetime if ttl doesnt exists (rafaeltovar) + * bug #34755 [FrameworkBundle] resolve service locators in `debug:*` commands (nicolas-grekas) + * bug #34832 [Validator] Allow underscore character "_" in URL username and password (romainneutron) + * bug #34765 [DoctrineBridge] Removed QueryBuilder type hint in getLoader() (HeahDude) + * bug #34811 [TwigBridge] Update bootstrap_4_layout.html.twig missing switch-custom label (sabruss) + * bug #34820 [FrameworkBundle][SodiumVault] Create secrets directory only when it is used (fancyweb) + * bug #34776 [DI] fix resolving bindings for named TypedReference (nicolas-grekas) + * bug #34794 [DependencyInjection] Resolve expressions in CheckTypeDeclarationsPass (fancyweb) + * bug #34795 [Routing][ObjectLoader] Remove forgotten deprecation after merge (fancyweb) + * bug #34797 [Translation] Fix FileDumper behavior (yceruto) + * bug #34738 [SecurityBundle] Passwords are not encoded when algorithm set to "true" (nieuwenhuisen) + * bug #34759 [SecurityBundle] Fix switch_user provider configuration handling (fancyweb) + * bug #34779 [Security] do not validate passwords when the hash is null (xabbuh) + * bug #34786 [SecurityBundle] Use config variable in AnonymousFactory (martijnboers) + * bug #34784 [FrameworkBundle] Set the parameter bag as resolved in ContainerLintCommand (fancyweb) + * bug #34763 [Security/Core] Fix checking for SHA256/SHA512 passwords (David Brooks) + * bug #34757 [DI] Fix making the container path-independent when the app is in /app (nicolas-grekas) + +* 5.0.1 (2019-12-01) + + * bug #34732 [DependencyInjection][Xml] Fix the attribute 'tag' is not allowed in 'bind' tag (tienvx) + * bug #34729 [DI] auto-register singly implemented interfaces by default (nicolas-grekas) + * bug #34728 [DI] fix overriding existing services with aliases for singly-implemented interfaces (nicolas-grekas) + * bug #34649 more robust initialization from request (dbu) + * bug #34715 [TwigBundle] remove service when base class is missing (xabbuh) + * bug #34600 [DoctrineBridge] do not depend on the QueryBuilder from the ORM (xabbuh) + * bug #34627 [Security/Http] call auth listeners/guards eagerly when they "support" the request (nicolas-grekas) + * bug #34671 [Security] Fix clearing remember-me cookie after deauthentication (chalasr) + * bug #34711 Fix the translation commands when a template contains a syntax error (fabpot) + * bug #34032 [Mime] Fixing multidimensional array structure with FormDataPart (jvahldick) + * bug #34697 [MonologBridge] Fix compatibility of ServerLogHandler with Monolog 2 (jderusse) + * bug #34560 [Config][ReflectionClassResource] Handle parameters with undefined constant as their default values (fancyweb) + * bug #34695 [Config] don't break on virtual stack frames in ClassExistenceResource (nicolas-grekas) + * bug #34716 [DependencyInjection] fix dumping number-like string parameters (xabbuh) + * bug #34558 [Console] Fix autocomplete multibyte input support (fancyweb) + * bug #34130 [Console] Fix commands description with numeric namespaces (fancyweb) + * bug #34562 [DI] Skip unknown method calls for factories in check types pass (fancyweb) + * bug #34677 [EventDispatcher] Better error reporting when arguments to dispatch() are swapped (rimas-kudelis) + * bug #33573 [TwigBridge] Add row_attr to all form themes (fancyweb) + * bug #34019 [Serializer] CsvEncoder::NO_HEADERS_KEY ignored when used in constructor (Dario Savella) + * bug #34083 [Form] Keep preferred_choices order for choice groups (vilius-g) + * bug #34091 [Debug] work around failing chdir() on Darwin (mary2501) + * bug #34305 [PhpUnitBridge] Read configuration CLI directive (ro0NL) + * bug #34490 [Serializer] Fix MetadataAwareNameConverter usage with string group (antograssiot) + * bug #34632 [Console] Fix trying to access array offset on value of type int (Tavafi) + * bug #34669 [HttpClient] turn exception into log when the request has no content-type (nicolas-grekas) + * bug #34662 [HttpKernel] Support typehint to deprecated FlattenException in controller (andrew-demb) + * bug #34619 Restores preview mode support for Html and Serializer error renderers (yceruto) + * bug #34636 [VarDumper] notice on potential undefined index (sylvainmetayer) + * bug #34668 [Cache] Make sure we get the correct number of values from redis::mget() (thePanz) + * bug #34621 [Routing] Continue supporting single colon in object route loaders (fancyweb) + * bug #34554 [HttpClient] Fix early cleanup of pushed HTTP/2 responses (lyrixx) + * bug #34607 [HttpKernel] Ability to define multiple kernel.reset tags (rmikalkenas) + * bug #34599 [Mailer][Mailchimp Bridge] Throwing undefined index _id when setting message id (monteiro) + * bug #34569 [Workflow] Apply the same logic of precedence between the apply() and the buildTransitionBlockerList() method (lyrixx) + * bug #34580 [HttpKernel] Don't cache "not-fresh" state (nicolas-grekas) + * bug #34577 [FrameworkBundle][Cache] Don't deep-merge cache pools configuration (alxndrbauer) + * bug #34515 [DependencyInjection] definitions are valid objects (xabbuh) + * bug #34536 [SecurityBundle] Don't require a user provider for the anonymous listener (chalasr) + * bug #34533 [Monolog Bridge] Fixed accessing static property as non static. (Sander-Toonen) + * bug #34502 [FrameworkBundle][ContainerLint] Keep "removing" compiler passes (fancyweb) + * bug #34552 [Dotenv] don't fail when referenced env var does not exist (xabbuh) + * bug #34546 [Serializer] Add DateTimeZoneNormalizer into Dependency Injection (jewome62) + * bug #34547 [Messenger] Error when specified default bus is not among the configured (vudaltsov) + * bug #34513 [Validator] remove return type declaration from __sleep() (xabbuh) + * bug #34551 [Security] SwitchUser is broken when the User Provider always returns a valid user (tucksaun) + * bug #34570 [FrameworkBundle][Notifier] Fixing notifier email definition without mailer (chr-hertel) + * bug #34385 Avoid empty "If-Modified-Since" header in validation request (mpdude) + * bug #34458 [Validator] ConstraintValidatorTestCase: add missing return value to mocked validate method calls (ogizanagi) + * bug #34516 [HttpKernel] drop return type declaration (xabbuh) + * bug #34474 [Messenger] Ignore stamps in in-memory transport (tienvx) + +* 5.0.0 (2019-11-21) + + * bug #34464 [Form] group constraints when calling the validator (nicolas-grekas) + * bug #34451 [DependencyInjection] Fix dumping multiple deprecated aliases (shyim) + * bug #34448 [Form] allow button names to start with uppercase letter (xabbuh) + * bug #34434 [Routing] Fix ContainerLoader and ObjectLoaderTest (fancyweb) + * bug #34428 [Security] Fix best encoder not wired using migrate_from (chalasr) + +* 5.0.0-RC1 (2019-11-17) + + * bug #34419 [Cache] Disable igbinary on PHP >= 7.4 (nicolas-grekas) + * bug #34347 [Messenger] Perform no deep merging of bus middleware (vudaltsov) + * bug #34366 [HttpFoundation] Allow redirecting to URLs that contain a semicolon (JayBizzle) + * feature #34405 [HttpFoundation] Added possibility to configure expiration time in redis session handler (mantulo) + * bug #34397 [FrameworkBundle] Remove project dir from Translator cache vary scanned directories (fancyweb) + * bug #34384 [DoctrineBridge] Improve queries parameters display in Profiler (fancyweb) + * bug #34408 [Cache] catch exceptions when using PDO directly (xabbuh) + * bug #34411 [HttpKernel] Flatten "exception" controller argument if not typed (chalasr) + * bug #34410 [HttpFoundation] Fix MySQL column type definition. (jbroutier) + * bug #34403 [Cache] Redis Tag Aware warn on wrong eviction policy (andrerom) + * bug #34400 [HttpKernel] collect bundle classes, not paths (nicolas-grekas) + * bug #34398 [Config] fix id-generation for GlobResource (nicolas-grekas) + * bug #34404 [HttpClient] fix HttpClientDataCollector (nicolas-grekas) + * bug #34396 [Finder] Allow ssh2 stream wrapper for sftp (damienalexandre) + * bug #34383 [DI] Use reproducible entropy to generate env placeholders (nicolas-grekas) + * bug #34389 [WebProfilerBundle] add FrameworkBundle requirement (xabbuh) + * bug #34381 [WebProfilerBundle] Require symfony/twig-bundle (fancyweb) + * bug #34358 [Security] always check the token on non-lazy firewalls (nicolas-grekas, lyrixx) + * bug #34390 [FrameworkBundle] fix wiring of httplug client (nicolas-grekas) + * bug #34369 [FrameworkBundle] Disallow WebProfilerBundle < 4.4 (derrabus) + * bug #34370 [DI] fix detecting singly implemented interfaces (nicolas-grekas) + +* 5.0.0-BETA2 (2019-11-13) + + * bug #34344 [Console] Constant STDOUT might be undefined (nicolas-grekas) + * bug #34348 [Serializer] Fix ProblemNormalizer signature mismatch (chalasr) + * security #cve-2019-18886 [Security\Core] throw AccessDeniedException when switch user fails (nicolas-grekas) + * security #cve-2019-18888 [Mime] fix guessing mime-types of files with leading dash (nicolas-grekas) + * security #cve-2019-11325 [VarExporter] fix exporting some strings (nicolas-grekas) + * security #cve-2019-18889 [Cache] forbid serializing AbstractAdapter and TagAwareAdapter instances (nicolas-grekas) + * security #cve-2019-18888 [HttpFoundation] fix guessing mime-types of files with leading dash (nicolas-grekas) + * security #cve-2019-18887 [HttpKernel] Use constant time comparison in UriSigner (stof) + +* 5.0.0-BETA1 (2019-11-12) + + * feature #34333 Revert "feature #34329 [ExpressionLanguage] add XOR operator (ottaviano)" (nicolas-grekas) + * feature #34332 Allow \Throwable $previous everywhere (fancyweb) + * feature #34329 [ExpressionLanguage] add XOR operator (ottaviano) + * feature #34312 [ErrorHandler] merge and remove the ErrorRenderer component (nicolas-grekas, yceruto) + * feature #34309 [HttpKernel] make ExceptionEvent able to propagate any throwable (nicolas-grekas) + * feature #33497 [Contracts] Add parameter type declarations to contracts (derrabus) + * feature #34139 [Security] Add migrating encoder configuration (chalasr) + * feature #32194 [HttpFoundation] Add a way to anonymize IPs (Seldaek) + * feature #34252 [Console] Add support for NO_COLOR env var (Seldaek) + * feature #34295 [DI][FrameworkBundle] add EnvVarLoaderInterface - remove SecretEnvVarProcessor (nicolas-grekas) + * feature #31310 [DependencyInjection] Added option `ignore_errors: not_found` for imported config files (pulzarraider) + * feature #34216 [HttpClient] allow arbitrary JSON values in requests (pschultz) + * feature #31977 Add handling for delayed message to redis transport (alexander-schranz) + * feature #34217 [Messenger] use events consistently in worker (Tobion) + * feature #33065 Deprecate things that prevent \Throwable from bubbling down (fancyweb) + * feature #34184 [VarDumper] display the method we're in when dumping stack traces (nicolas-grekas) + * feature #33732 [Console] Rename some methods related to redraw frequency (javiereguiluz) + * feature #31587 [Routing][Config] Allow patterns of resources to be excluded from config loading (tristanbes) + * feature #32256 [DI] Add compiler pass and command to check that services wiring matches type declarations (alcalyn, GuilhemN, nicolas-grekas) + * feature #32061 Add new Form WeekType (dFayet) + * feature #33954 Form theme: support Bootstrap 4 custom switches (romaricdrigon) + * feature #33854 [DI] Add ability to choose behavior of decorations on non existent decorated services (mtarld) + * feature #34185 [Messenger] extract worker logic to listener and get rid of SendersLocatorInterface::getSenderByAlias (Tobion) + * feature #34156 Adding DoctrineClearEntityManagerWorkerSubscriber to reset EM in worker (weaverryan) + * feature #34133 [Cache] add DeflateMarshaller - remove phpredis compression (nicolas-grekas) + * feature #34177 [HttpFoundation][FrameworkBundle] allow configuring the session handler with a DSN (nicolas-grekas) + * feature #32107 [Validator] Add AutoMapping constraint to enable or disable auto-validation (dunglas) + * feature #34170 Re-allow to use "tagged" in service definitions (dunglas) + * feature #34043 [Lock] Add missing lock connection string in FrameworkExtension (jderusse) + * feature #34057 [Lock][Cache] Allows URL DSN in PDO adapters (jderusse) + * feature #34151 [DomCrawler] normalizeWhitespace should be true by default (dunglas) + * feature #34020 [Security] Allow to stick to a specific password hashing algorithm (chalasr) + * feature #34141 Slack notifier actions (fabpot) + * feature #34131 [FrameworkBundle] Remove suffix convention when using env vars to override secrets from the vault (nicolas-grekas) + * feature #34051 [HttpClient] allow option "buffer" to be a stream resource (nicolas-grekas) + * feature #34028 [ExpressionLanguage][Lexer] Exponential format for number (tigr1991) + * feature #34069 [Messenger] Removing "sync" transport and replacing it with config trick (weaverryan) + * feature #34014 [DI] made the `env(base64:...)` processor able to decode base64url (nicolas-grekas) + * feature #34044 [HttpClient] Add a canceled state to the ResponseInterface (Toflar) + * feature #33997 [FrameworkBundle] Add `secrets:*` commands and `env(secret:...)` processor to deal with secrets seamlessly (Tobion, jderusse, nicolas-grekas) + * feature #34013 [DI] add `LazyString` for lazy computation of string values injected into services (nicolas-grekas) + * feature #33961 [TwigBridge] Add show-deprecations option to the lint:twig command (yceruto) + * feature #33973 [HttpClient] add HttpClient::createForBaseUri() (nicolas-grekas) + * feature #33980 [HttpClient] try using php-http/discovery when nyholm/psr7 is not installed (nicolas-grekas) + * feature #33967 [Mailer] Add Message-Id to SentMessage when sending an email (fabpot) + * feature #33896 [Serializer][CSV] Add context options to handle BOM (malarzm) + * feature #33883 [Mailer] added ReplyTo option for PostmarkApiTransport (pierregaste) + * feature #33053 [ErrorHandler] Rework fatal errors (fancyweb) + * feature #33939 [Cache] add TagAwareMarshaller to optimize data storage when using AbstractTagAwareAdapter (nicolas-grekas) + * feature #33941 Keeping backward compatibility with legacy FlattenException usage (yceruto) + * feature #33851 [EventDispatcher] Allow to omit the event name when registering listeners (derrabus) + * feature #33461 [Cache] Improve RedisTagAwareAdapter invalidation logic & requirements (andrerom) + * feature #33779 [DI] enable improved syntax for defining method calls in Yaml (nicolas-grekas) + * feature #33743 [HttpClient] Async HTTPlug client (Nyholm) + * feature #33856 [Messenger] Allow to configure the db index on Redis transport (chalasr) + * feature #33881 [VarDumper] Added a support for casting Ramsey/Uuid (lyrixx) + * feature #33687 Notifier Component (fabpot) + * feature #33861 [CssSelector] Support *:only-of-type (jakzal) + * feature #33793 [EventDispatcher] A compiler pass for aliased userland events (derrabus) + * feature #33791 [Form] Added CountryType option for using alpha3 country codes (creiner) + * feature #33628 [DependencyInjection] added Ability to define a priority method for tagged service (lyrixx) + * feature #33768 [String] Introduce a locale-aware Slugger in the String component (tgalopin) + * feature #33775 [Console] Add deprecation message for non-int statusCode (jschaedl) + * feature #33783 [WebProfilerBundle] Try to display the most useful panel by default (fancyweb) + * feature #33701 [HttpKernel] wrap compilation of the container in an opportunistic lock (nicolas-grekas) + * feature #33789 [Serializer] Deprecate the XmlEncoder::TYPE_CASE_ATTRIBUTES constant (pierredup) + * feature #31446 [VarDumper] Output the location of calls to dump() (ktherage) + * feature #33412 [Console] Do not leak hidden console commands (m-vo) + * feature #33676 [Security] add "anonymous: lazy" mode to firewalls (nicolas-grekas) + * feature #32440 [DomCrawler] add a normalizeWhitespace argument to text() method (Simperfit) + * feature #33148 [Intl] Excludes locale from language codes (split localized language names) (ro0NL) + * feature #31202 [FrameworkBundle] WebTestCase KernelBrowser::getContainer null return type (Simperfit) + * feature #33038 [ErrorHandler] Forward \Throwable (fancyweb) + * feature #33574 [Http][DI] Replace REMOTE_ADDR in trusted proxies with the current REMOTE_ADDR (mcfedr) + * feature #33553 [String] a new component for object-oriented strings management with an abstract unit system (nicolas-grekas, hhamon, gharlan) + * feature #33113 [Messenger][DX] Display real handler if handler is wrapped (DavidBadura) + * feature #33128 [FrameworkBundle] Sort tagged services (krome162504) + * feature #33658 [Yaml] fix parsing inline YAML spanning multiple lines (xabbuh) + * feature #33698 [HttpKernel] compress files generated by the profiler (nicolas-grekas) + * feature #33317 [Messenger] Added support for `from_transport` attribute on `messenger.message_handler` tag (ruudk) + * feature #33584 [Security] Deprecate isGranted()/decide() on more than one attribute (wouterj) + * feature #33663 [Security] Make stateful firewalls turn responses private only when needed (nicolas-grekas) + * feature #33609 [Form][SubmitType] Add "validate" option (fancyweb) + * feature #33621 Revert "feature #33507 [WebProfiler] Deprecated intercept_redirects in 4.4 (dorumd)" (lyrixx) + * feature #33635 [FrameworkBundle] Cleanup (yceruto) + * feature #33605 [Twig] Add NotificationEmail (fabpot) + * feature #33623 [DependencyInjection] Allow binding iterable and tagged services (lyrixx) + * feature #33507 [WebProfiler] Deprecated intercept_redirects in 4.4 (dorumd) + * feature #33579 Adding .gitattributes to remove Tests directory from "dist" (Nyholm) + * feature #33562 [Mailer] rename SmtpEnvelope to Envelope (xabbuh) + * feature #33565 [Mailer] Rename an exception class (fabpot) + * feature #33516 [Cache] Added reserved characters constant for CacheItem (andyexeter) + * feature #33503 [SecurityBundle] Move Anonymous DI integration to new AnonymousFactory (wouterj) + * feature #33535 [WebProfilerBundle] Assign automatic colors to custom Stopwatch categories (javiereguiluz) + * feature #32565 [HttpClient] Allow enabling buffering conditionally with a Closure (rjwebdev) + * feature #32032 [DI] generate preload.php file for PHP 7.4 in cache folder (nicolas-grekas) + * feature #33117 [FrameworkBundle] Added --sort option for TranslationUpdateCommand (k0d3r1s) + * feature #32832 [Serializer] Allow multi-dimenstion object array in AbstractObjectNormalizer (alediator) + * feature #33189 New welcome page on startup for 4.4 LTS & 5.0 (yceruto) + * feature #33295 [OptionsResolver] Display full nested option hierarchy in exceptions (fancyweb) + * feature #33486 [VarDumper] Display fully qualified title (pavinthan, nicolas-grekas) + * feature #33496 Deprecated not passing dash symbol (-) to STDIN commands (yceruto) + * feature #32742 [Console] Added support for definition list and horizontal table (lyrixx) + * feature #33494 [Mailer] Change DSN syntax (fabpot) + * feature #33471 [Mailer] Check email validity before opening an SMTP connection (fabpot) + * feature #31177 #21571 Comparing roles to detected that users has changed (oleg-andreyev) + * feature #33459 [Validator] Deprecated CacheInterface in favor of PSR-6 (derrabus) + * feature #33271 Added new ErrorController + Preview and enabling there the error renderer mechanism (yceruto) + * feature #33454 [Mailer] Improve an exception when trying to send a RawMessage without an Envelope (fabpot) + * feature #33327 [ErrorHandler] Registering basic exception handler for late failures (yceruto) + * feature #33446 [TwigBridge] lint all templates from configured Twig paths if no argument was provided (yceruto) + * feature #33409 [Mailer] Add support for multiple mailers (fabpot) + * feature #33424 [Mailer] Change the DSN semantics (fabpot) + * feature #33352 [Security] drop support for non-boolean return values from checkCredentials() (xabbuh) + * feature #33319 Allow configuring class names through methods instead of class parameters in Doctrine extensions (alcaeus) + * feature #33283 [ErrorHandler] make DebugClassLoader able to add return type declarations (nicolas-grekas) + * feature #33323 [TwigBridge] Throw an exception when one uses email as a context variable in a TemplatedEmail (fabpot) + * feature #33308 [SecurityGuard] Deprecate returning non-boolean values from checkCredentials() (derrabus) + * feature #33217 [FrameworkBundle][DX] Improving the redirect config when using RedirectController (yceruto) + * feature #33015 [HttpClient] Added TraceableHttpClient and WebProfiler panel (jeremyFreeAgent) + * feature #33091 [Mime] Add Address::fromString (gisostallenberg) + * feature #33144 [DomCrawler] Added Crawler::matches(), ::closest(), ::outerHtml() (lyrixx) + * feature #33152 Mark all dispatched event classes as final (Tobion) + * feature #33258 [HttpKernel] deprecate global dir to load resources from (Tobion) + * feature #33272 [Translation] deprecate support for null locales (xabbuh) + * feature #33269 [TwigBridge] Mark all classes extending twig as @final (fabpot) + * feature #33270 [Mime] Remove NamedAddress (fabpot) + * feature #33169 [HttpFoundation] Precalculate session expiry timestamp (azjezz) + * feature #33237 [Mailer] Remove the auth mode DSN option and support in the eSMTP transport (fabpot) + * feature #33233 [Mailer] Simplify the way TLS/SSL/STARTTLS work (fabpot) + * feature #32360 [Monolog] Added ElasticsearchLogstashHandler (lyrixx) + * feature #32489 [Messenger] Allow exchange type headers binding (CedrickOka) + * feature #32783 [Messenger] InMemoryTransport handle acknowledged and rejected messages (tienvx) + * feature #33098 added `Process::getLastOutputTime()` method (connorhu) + * feature #33155 [ErrorHandler] Added call() method utility to turns any PHP error into \ErrorException (yceruto) + * feature #33203 [Mailer] Add support for the queued flag in the EmailCount assertion (fabpot) + * feature #30323 [ErrorHandler] trigger deprecation in DebugClassLoader when child class misses a return type (fancyweb, nicolas-grekas) + * feature #33137 [DI] deprecate support for non-object services (nicolas-grekas) + * feature #32845 [HttpKernel][FrameworkBundle] Add alternative convention for bundle directories (yceruto) + * feature #32548 [Translation] XliffLintCommand: allow .xliff file extension (codegain) + * feature #28363 [Serializer] Encode empty objects as objects, not arrays (mcfedr) + * feature #33122 [WebLink] implement PSR-13 directly (nicolas-grekas) + * feature #33078 Add compatibility trait for PHPUnit constraint classes (alcaeus) + * feature #32988 [Intl] Support ISO 3166-1 Alpha-3 country codes (terjebraten-certua) + * feature #32598 [FrameworkBundle][Routing] Private service route loaders (fancyweb) + * feature #32486 [DoctrineBridge] Invokable event listeners (fancyweb) + * feature #31083 [Validator] Allow objects implementing __toString() to be used as violation messages (mdlutz24) + * feature #32122 [HttpFoundation] deprecate HeaderBag::get() returning an array and add all($key) instead (Simperfit) + * feature #32807 [HttpClient] add "max_duration" option (fancyweb) + * feature #31546 [Dotenv] Use default value when referenced variable is not set (j92) + * feature #32930 [Mailer][Mime] Add PHPUnit constraints and assertions for the Mailer (fabpot) + * feature #32912 [Mailer] Add support for the profiler (fabpot) + * feature #32940 [PhpUnitBridge] Add polyfill for PhpUnit namespace (jderusse) + * feature #31843 [Security] add support for opportunistic password migrations (nicolas-grekas) + * feature #32824 [Ldap] Add security LdapUser and provider (chalasr) + * feature #32922 [PhpUnitBridge] make the bridge act as a polyfill for newest PHPUnit features (nicolas-grekas) + * feature #32927 [Mailer] Add message events logger (fabpot) + * feature #32916 [Mailer] Add a name to the transports (fabpot) + * feature #32917 [Mime] Add AbstractPart::asDebugString() (fabpot) + * feature #32543 [FrameworkBundle] add config translator cache_dir (Raulnet) + * feature #32669 [Yaml] Add flag to dump NULL as ~ (OskarStark) + * feature #32896 [Mailer] added debug info to TransportExceptionInterface (fabpot) + * feature #32817 [DoctrineBridge] Deprecate RegistryInterface (Koc) + * feature #32504 [ErrorRenderer] Add DebugCommand for easy debugging and testing (yceruto) + * feature #32581 [DI] Allow dumping the container in one file instead of many files (nicolas-grekas) + * feature #32762 [Form][DX] derive default timezone from reference_date option when possible (yceruto) + * feature #32745 [Messenger][Profiler] Attempt to give more useful source info when using HandleTrait (ogizanagi) + * feature #32680 [Messenger][Profiler] Collect the stamps at the end of dispatch (ogizanagi) + * feature #32683 [VarDumper] added support for Imagine/Image (lyrixx) + * feature #32749 [Mailer] Make transport factory test case public (Koc) + * feature #32718 [Form] use a reference date to handle times during DST (xabbuh) + * feature #32637 [ErrorHandler] Decouple from ErrorRenderer component (yceruto) + * feature #32609 [Mailer][DX][RFC] Rename mailer bridge transport classes (Koc) + * feature #32587 [Form][Validator] Generate accept attribute with file constraint and mime types option (Coosos) + * feature #32658 [Form] repeat preferred choices in list of all choices (Seb33300, xabbuh) + * feature #32698 [WebProfilerBundle] mark all classes as internal (Tobion) + * feature #32695 [WebProfilerBundle] Decoupling TwigBundle and using the new ErrorRenderer mechanism (yceruto) + * feature #31398 [TwigBundle] Deprecating error templates for non-html formats and using ErrorRenderer as fallback (yceruto) + * feature #32582 [Routing] Deprecate ServiceRouterLoader and ObjectRouteLoader in favor of ContainerLoader and ObjectLoader (fancyweb) + * feature #32661 [ErrorRenderer] Improving the exception page provided by HtmlErrorRenderer (yceruto) + * feature #32332 [DI] Move non removing compiler passes to after removing passes (alexpott) + * feature #32475 [Process] Deprecate Process::inheritEnvironmentVariables() (ogizanagi) + * feature #32583 [Mailer] Logger vs debug mailer (fabpot) + * feature #32471 Add a new ErrorHandler component (mirror of the Debug component) (yceruto) + * feature #32463 [VarDumper] Allow to configure VarDumperTestTrait casters & flags (ogizanagi) + * feature #31946 [Mailer] Extract transport factory and allow create custom transports (Koc) + * feature #31194 [PropertyAccess] Improve errors when trying to find a writable property (pierredup) + * feature #32435 [Validator] Add a new constraint message when there is both min and max (Lctrs) + * feature #32470 Rename ErrorCatcher to ErrorRenderer (rendering part only) (yceruto) + * feature #32462 [WebProfilerBundle] Deprecating templateExists method (yceruto) + * feature #32458 Remove support for Twig 1.x (fabpot) + * feature #32446 [Lock] rename and deprecate Factory into LockFactory (Simperfit) + * feature #31975 Dynamic bundle assets (garak) + * feature #32429 [VarDumper] Let browsers trigger their own search on double CMD/CTRL + F (ogizanagi) + * feature #32198 [Lock] Split "StoreInterface" into multiple interfaces with less responsibility (Simperfit) + * feature #31511 [Validator] Allow to use property paths to get limits in range constraint (Lctrs) + * feature #32424 [Console] don't redraw progress bar more than every 100ms by default (nicolas-grekas) + * feature #27905 [MonologBridge] Monolog 2 compatibility (derrabus) + * feature #32418 [Console] Added Application::reset() (lyrixx) + * feature #31217 [WebserverBundle] Deprecate the bundle in favor of symfony local server (Simperfit) + * feature #31554 [SECURITY] AbstractAuthenticationListener.php error instead info. Rebase of #28462 (berezuev) + * feature #32284 [Cache] Add argument $prefix to AdapterInterface::clear() (nicolas-grekas) + * feature #32423 [ServerBundle] Display all logs by default (lyrixx) + * feature #26339 [Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods (ostrolucky) + * feature #31269 [Translator] Dump native plural formats to po files (Stadly) + * feature #31560 [Ldap][Security] LdapBindAuthenticationProvider does not bind before search query (Simperfit) + * feature #31626 [Console] allow answer to be trimmed by adding a flag (Simperfit) + * feature #31876 [WebProfilerBundle] Add clear button to ajax tab (Matts) + * feature #32415 [Translation] deprecate passing a null locale (Simperfit) + * feature #32290 [HttpClient] Add $response->toStream() to cast responses to regular PHP streams (nicolas-grekas) + * feature #32402 [Intl] Exclude root language (ro0NL) + * feature #32295 [FrameworkBundle] Add autowiring alias for PSR-14 (nicolas-grekas) + * feature #32390 [DependencyInjection] Deprecated passing Parameter instances as class name to Definition (derrabus) + * feature #32106 [FrameworkBundle] Use default_locale as default value for translator.fallbacks (dunglas) + * feature #32294 [FrameworkBundle] Allow creating chained cache pools by providing several adapters (nicolas-grekas) + * feature #32373 [Validator] Change Length::$allowEmptyString default to false & make it optional (ogizanagi) + * feature #32207 [FrameworkBundle] Allow to use the BrowserKit assertions with Panther and API Platform's test client (dunglas) + * feature #32344 [HttpFoundation][HttpKernel] Improving the request/response format autodetection (yceruto) + * feature #32231 [HttpClient] Add support for NTLM authentication (nicolas-grekas) + * feature #32265 [Validator] deprecate non-string constraint violation codes (xabbuh) + * feature #31528 [Validator] Add a Length::$allowEmptyString option to reject empty strings (ogizanagi) + * feature #32081 [WIP][Mailer] Overwrite envelope sender and recipients from config (Devristo) + * feature #32255 [HttpFoundation] Drop support for ApacheRequest (lyrixx) + * feature #31825 [Messenger] Added support for auto trimming of redis streams (Toflar) + * feature #32277 Remove @experimental annotations (fabpot) + * feature #30981 [Mime] S/MIME Support (sstok) + * feature #32180 [Lock] add an InvalidTTLException to be more accurate (Simperfit) + * feature #32241 [PropertyAccess] Deprecate null as allowed value for defaultLifetime argument in createCache method (jschaedl) + * feature #32221 [ErrorCatcher] Make IDEs and static analysis tools happy (fabpot) + * feature #32227 Rename the ErrorHandler component to ErrorCatcher (fabpot) + * feature #31065 Add ErrorHandler component (yceruto) + * feature #32126 [Process] Allow writing portable "prepared" command lines (Simperfit) + * feature #31996 Add return types in final classes (dFayet) + * feature #31532 [Ldap] Add users extraFields in ldap component (Simperfit) + * feature #32104 Add autowiring for HTTPlug (nicolas-grekas) + * feature #32130 [Form] deprecate int/float for string input in NumberType (xabbuh) + * feature #31547 [Ldap] Add exception for mapping ldap errors (Simperfit) + * feature #31764 [FrameworkBundle] add attribute stamps (walidboughdiri) + * feature #32059 [PhpUnitBridge] Bump PHPUnit 7+8 (ro0NL) + * feature #32041 [Validator] Deprecate unused arg in ExpressionValidator (ogizanagi) + * feature #31287 [Config] Introduce find method in ArrayNodeDefinition to ease configuration tree manipulation (jschaedl) + * feature #31959 [DomCrawler][Feature][DX] Add Form::getName() method (JustBlackBird) + * feature #32026 [VarDumper] caster for HttpClient's response dumps all info (nicolas-grekas) + * feature #31976 [HttpClient] add HttplugClient for compat with libs that need httplug v1 or v2 (nicolas-grekas) + * feature #31956 [Mailer] Changed EventDispatcherInterface dependency from Component to Contracts (Koc) + * feature #31980 [HttpClient] make Psr18Client implement relevant PSR-17 factories (nicolas-grekas) + * feature #31919 [WebProfilerBundle] Select default theme based on user preferences (javiereguiluz) + * feature #31451 [FrameworkBundle] Allow dots in translation domains (jschaedl) + * feature #31321 [DI] deprecates tag !tagged in favor of !tagged_iterator (jschaedl) + * feature #31658 [HTTP Foundation] Deprecate passing argument to method Request::isMethodSafe() (dFayet) + * feature #31597 [Security] add MigratingPasswordEncoder (nicolas-grekas) + * feature #31351 [Validator] Improve TypeValidator to handle array of types (jschaedl) + * feature #31526 [Validator] Add compared value path to violation parameters (ogizanagi) + * feature #31514 Add exception as HTML comment to beginning and end of `exception_full.html.twig` (ruudk) + * feature #31739 [FrameworkBundle] Add missing BC layer for deprecated ControllerNameParser injections (chalasr) + * feature #31831 [HttpClient] add $response->cancel() (nicolas-grekas) + * feature #31334 [Messenger] Add clear Entity Manager middleware (Koc) + * feature #31800 Removed support for PHP templating everywhere (yceruto) + * feature #31594 [Security] add PasswordEncoderInterface::needsRehash() (nicolas-grekas) + * feature #31821 [FrameworkBundle][TwigBundle] Add missing deprecations for PHP templating layer (yceruto) + * feature #31509 [Monolog] Setup the LoggerProcessor after all other processor (lyrixx) + * feature #31777 [Form] remove deprecated date types options handling (xabbuh) + * feature #31785 [Messenger] Deprecate passing a bus locator to ConsumeMessagesCommand's constructor (chalasr) + * feature #31700 [MonologBridge] RouteProcessor class is now final to ease the the removal of deprecated event (Simperfit) + * feature #31732 [HttpKernel] Make DebugHandlersListener internal (chalasr) + * feature #31539 [HttpKernel] Add lts config (noniagriconomie) + * feature #31437 [Cache] Add Redis Sentinel support (StephenClouse) + * feature #31543 [DI] deprecate short callables in yaml (nicolas-grekas) + diff --git a/UPGRADE-5.1.md b/UPGRADE-5.1.md new file mode 100644 index 0000000000000..169f5b683d7b9 --- /dev/null +++ b/UPGRADE-5.1.md @@ -0,0 +1,86 @@ +UPGRADE FROM 5.0 to 5.1 +======================= + +Console +------- + + * `Command::setHidden()` is final since Symfony 5.1 + +Dotenv +------ + + * Deprecated passing `$usePutenv` argument to Dotenv's constructor, use `Dotenv::usePutenv()` instead. + +EventDispatcher +--------------- + + * Deprecated `LegacyEventDispatcherProxy`. Use the event dispatcher without the proxy. + +Form +---- + + * Implementing the `FormConfigInterface` without implementing the `getIsEmptyCallback()` method + is deprecated. The method will be added to the interface in 6.0. + * Implementing the `FormConfigBuilderInterface` without implementing the `setIsEmptyCallback()` method + is deprecated. The method will be added to the interface in 6.0. + +FrameworkBundle +--------------- + + * Deprecated passing a `RouteCollectionBuiler` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead + * Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0 + +HttpFoundation +-------------- + + * Deprecate `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use + `__construct()` instead) + * Made the Mime component an optional dependency + +Messenger +--------- + + * Deprecated AmqpExt transport. It has moved to a separate package. Run `composer require symfony/amqp-messenger` to use the new classes. + * Deprecated Doctrine transport. It has moved to a separate package. Run `composer require symfony/doctrine-messenger` to use the new classes. + * Deprecated RedisExt transport. It has moved to a separate package. Run `composer require symfony/redis-messenger` to use the new classes. + * Deprecated use of invalid options in Redis and AMQP connections. + +Notifier +-------- + + * [BC BREAK] The `ChatMessage::fromNotification()` method's `$recipient` and `$transport` + arguments were removed. + * [BC BREAK] The `EmailMessage::fromNotification()` and `SmsMessage::fromNotification()` + methods' `$transport` argument was removed. + +Routing +------- + + * Deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`. + * Added argument `$priority` to `RouteCollection::add()` + * Deprecated the `RouteCompiler::REGEX_DELIMITER` constant + +Security +-------- + + * Deprecated `ROLE_PREVIOUS_ADMIN` role in favor of `IS_IMPERSONATOR` attribute. + + *before* + ```twig + {% if is_granted('ROLE_PREVIOUS_ADMIN') %} + Exit impersonation + {% endif %} + ``` + + *after* + ```twig + {% if is_granted('IS_IMPERSONATOR') %} + Exit impersonation + {% endif %} + ``` + +Yaml +---- + + * Deprecated using the `!php/object` and `!php/const` tags without a value. diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md new file mode 100644 index 0000000000000..36eb66645d622 --- /dev/null +++ b/UPGRADE-6.0.md @@ -0,0 +1,56 @@ +UPGRADE FROM 5.x to 6.0 +======================= + +Console +------- + + * `Command::setHidden()` has a default value (`true`) for `$hidden` parameter + +Dotenv +------ + + * Removed argument `$usePutenv` from Dotenv's constructor, use `Dotenv::usePutenv()` instead. + +EventDispatcher +--------------- + + * Removed `LegacyEventDispatcherProxy`. Use the event dispatcher without the proxy. + +Form +---- + + * Added the `getIsEmptyCallback()` method to the `FormConfigInterface`. + * Added the `setIsEmptyCallback()` method to the `FormConfigBuilderInterface`. + +FrameworkBundle +--------------- + + * `MicroKernelTrait::configureRoutes()` is now always called with a `RoutingConfigurator` + * The "framework.router.utf8" configuration option defaults to `true` + +HttpFoundation +-------------- + + * Removed `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use + `__construct()` instead) + +Messenger +--------- + + * Removed AmqpExt transport. Run `composer require symfony/amqp-messenger` to keep the transport in your application. + * Removed Doctrine transport. Run `composer require symfony/doctrine-messenger` to keep the transport in your application. + * Removed RedisExt transport. Run `composer require symfony/redis-messenger` to keep the transport in your application. + * Use of invalid options in Redis and AMQP connections now throws an error. + +Routing +------- + + * Removed `RouteCollectionBuilder`. + * Added argument `$priority` to `RouteCollection::add()` + * Removed the `RouteCompiler::REGEX_DELIMITER` constant + +Security +-------- + + * Removed `ROLE_PREVIOUS_ADMIN` role in favor of `IS_IMPERSONATOR` attribute diff --git a/composer.json b/composer.json index 2b089d9d48fba..ff1bebc174e36 100644 --- a/composer.json +++ b/composer.json @@ -16,21 +16,23 @@ } ], "require": { - "php": "^7.1.3", + "php": "^7.2.5", "ext-xml": "*", "doctrine/event-manager": "~1.0", "doctrine/persistence": "^1.3", - "twig/twig": "^1.41|^2.10|^3.0", + "twig/twig": "^2.10|^3.0", "psr/cache": "~1.0", "psr/container": "^1.0", + "psr/event-dispatcher": "^1.0", "psr/link": "^1.0", "psr/log": "~1.0", - "symfony/contracts": "^1.1.8", + "symfony/contracts": "^2.1", "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php72": "~1.5", "symfony/polyfill-php73": "^1.11" }, "replace": { @@ -42,7 +44,6 @@ "symfony/console": "self.version", "symfony/css-selector": "self.version", "symfony/dependency-injection": "self.version", - "symfony/debug": "self.version", "symfony/debug-bundle": "self.version", "symfony/doctrine-bridge": "self.version", "symfony/dom-crawler": "self.version", @@ -68,6 +69,7 @@ "symfony/messenger": "self.version", "symfony/mime": "self.version", "symfony/monolog-bridge": "self.version", + "symfony/notifier": "self.version", "symfony/options-resolver": "self.version", "symfony/postmark-mailer": "self.version", "symfony/process": "self.version", @@ -75,7 +77,6 @@ "symfony/property-info": "self.version", "symfony/proxy-manager-bridge": "self.version", "symfony/routing": "self.version", - "symfony/security": "self.version", "symfony/security-core": "self.version", "symfony/security-csrf": "self.version", "symfony/security-guard": "self.version", @@ -84,6 +85,7 @@ "symfony/sendgrid-mailer": "self.version", "symfony/serializer": "self.version", "symfony/stopwatch": "self.version", + "symfony/string": "self.version", "symfony/templating": "self.version", "symfony/translation": "self.version", "symfony/twig-bridge": "self.version", @@ -93,11 +95,12 @@ "symfony/var-exporter": "self.version", "symfony/web-link": "self.version", "symfony/web-profiler-bundle": "self.version", - "symfony/web-server-bundle": "self.version", "symfony/workflow": "self.version", "symfony/yaml": "self.version" }, "require-dev": { + "amphp/http-client": "^4.2", + "amphp/http-tunnel": "^1.0", "cache/integration-tests": "dev-master", "doctrine/annotations": "~1.0", "doctrine/cache": "~1.6", @@ -106,10 +109,10 @@ "doctrine/dbal": "~2.4", "doctrine/orm": "~2.4,>=2.4.5", "doctrine/reflection": "~1.0", - "doctrine/doctrine-bundle": "^1.5|^2.0", + "doctrine/doctrine-bundle": "^2.0", "guzzlehttp/promises": "^1.3.1", "masterminds/html5": "^2.6", - "monolog/monolog": "^1.25.1", + "monolog/monolog": "^1.25.1|^2", "nyholm/psr7": "^1.0", "ocramius/proxy-manager": "^2.1", "paragonie/sodium_compat": "^1.8", @@ -127,8 +130,7 @@ }, "conflict": { "masterminds/html5": "<2.6", - "monolog/monolog": ">=2", - "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", + "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<0.3.0", "ocramius/proxy-manager": "<2.1", "phpunit/phpunit": "<5.4.3" @@ -150,7 +152,10 @@ ] }, "autoload-dev": { - "files": [ "src/Symfony/Component/VarDumper/Resources/functions/dump.php" ] + "files": [ + "src/Symfony/Component/String/Resources/functions.php", + "src/Symfony/Component/VarDumper/Resources/functions/dump.php" + ] }, "repositories": [ { @@ -161,7 +166,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7313d16d25c70..8aae634604ee8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,6 +21,9 @@ + + + diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 8fe8d410797fa..78ebd8b4b9e77 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +5.0.0 +----- + + * the `getMetadataDriverClass()` method is abstract and must be implemented by class extending `AbstractDoctrineExtension` + * passing an `IdReader` to the `DoctrineChoiceLoader` when the query cannot be optimized with single id field, throws an exception; pass `null` instead + * not explicitly passing an instance of `IdReader` to `DoctrineChoiceLoader` when it can optimize single id field, will not apply any optimization + * `DoctrineExtractor` now requires an `EntityManagerInterface` on instantiation + 4.4.0 ----- diff --git a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php index 24cf25be59f3d..0a90bc1a3f86c 100644 --- a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php +++ b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php @@ -44,7 +44,7 @@ public function isOptional() /** * {@inheritdoc} */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir) { foreach ($this->registry->getManagers() as $em) { // we need the directory no matter the proxy cache generation strategy diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index 31ba75891e1ea..67bf68da01596 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -46,20 +46,16 @@ public function __construct(ManagerRegistry $registry) /** * Adds the stack logger for a connection. - * - * @param string $name */ - public function addLogger($name, DebugStack $logger) + public function addLogger(string $name, DebugStack $logger) { $this->loggers[$name] = $logger; } /** * {@inheritdoc} - * - * @param \Throwable|null $exception */ - public function collect(Request $request, Response $response/*, \Throwable $exception = null*/) + public function collect(Request $request, Response $response, \Throwable $exception = null) { $queries = []; foreach ($this->loggers as $name => $logger) { diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 9a82d854152c2..23230f7e22fa0 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -100,11 +100,8 @@ protected function loadMappingInformation(array $objectManager, ContainerBuilder * Register the alias for this mapping driver. * * Aliases can be used in the Query languages of all the Doctrine object managers to simplify writing tasks. - * - * @param array $mappingConfig - * @param string $mappingName */ - protected function setMappingDriverAlias($mappingConfig, $mappingName) + protected function setMappingDriverAlias(array $mappingConfig, string $mappingName) { if (isset($mappingConfig['alias'])) { $this->aliasMap[$mappingConfig['alias']] = $mappingConfig['prefix']; @@ -116,11 +113,9 @@ protected function setMappingDriverAlias($mappingConfig, $mappingName) /** * Register the mapping driver configuration for later use with the object managers metadata driver chain. * - * @param string $mappingName - * * @throws \InvalidArgumentException */ - protected function setMappingDriverConfig(array $mappingConfig, $mappingName) + protected function setMappingDriverConfig(array $mappingConfig, string $mappingName) { $mappingDirectory = $mappingConfig['dir']; if (!is_dir($mappingDirectory)) { @@ -169,10 +164,8 @@ protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \Re /** * Register all the collected mapping information with the object manager by registering the appropriate mapping drivers. - * - * @param array $objectManager */ - protected function registerMappingDrivers($objectManager, ContainerBuilder $container) + protected function registerMappingDrivers(array $objectManager, ContainerBuilder $container) { // configure metadata driver for each bundle based on the type of mapping files found if ($container->hasDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'))) { @@ -222,11 +215,9 @@ protected function registerMappingDrivers($objectManager, ContainerBuilder $cont /** * Assertion if the specified mapping information is valid. * - * @param string $objectManagerName - * * @throws \InvalidArgumentException */ - protected function assertValidMappingConfiguration(array $mappingConfig, $objectManagerName) + protected function assertValidMappingConfiguration(array $mappingConfig, string $objectManagerName) { if (!$mappingConfig['type'] || !$mappingConfig['dir'] || !$mappingConfig['prefix']) { throw new \InvalidArgumentException(sprintf('Mapping definitions for Doctrine manager "%s" require at least the "type", "dir" and "prefix" options.', $objectManagerName)); @@ -244,11 +235,9 @@ protected function assertValidMappingConfiguration(array $mappingConfig, $object /** * Detects what metadata driver to use for the supplied directory. * - * @param string $dir A directory path - * * @return string|null A metadata driver short name, if one can be detected */ - protected function detectMetadataDriver($dir, ContainerBuilder $container) + protected function detectMetadataDriver(string $dir, ContainerBuilder $container) { $configPath = $this->getMappingResourceConfigDirectory(); $extension = $this->getMappingResourceExtension(); @@ -277,12 +266,9 @@ protected function detectMetadataDriver($dir, ContainerBuilder $container) /** * Loads a configured object manager metadata, query or result cache driver. * - * @param array $objectManager A configured object manager - * @param string $cacheName - * * @throws \InvalidArgumentException in case of unknown driver type */ - protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName) + protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, string $cacheName) { $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container); } @@ -290,15 +276,11 @@ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerB /** * Loads a cache driver. * - * @param string $cacheName The cache driver name - * @param string $objectManagerName The object manager name - * @param array $cacheDriver The cache driver mapping - * * @return string * * @throws \InvalidArgumentException */ - protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container) + protected function loadCacheDriver(string $cacheName, string $objectManagerName, array $cacheDriver, ContainerBuilder $container) { $cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName); @@ -401,11 +383,9 @@ protected function fixManagersAutoMappings(array $managerConfigs, array $bundles * * @example $name is 'entity_manager' then the result would be 'doctrine.orm.entity_manager' * - * @param string $name - * * @return string */ - abstract protected function getObjectManagerElementName($name); + abstract protected function getObjectManagerElementName(string $name); /** * Noun that describes the mapped objects such as Entity or Document. @@ -433,12 +413,7 @@ abstract protected function getMappingResourceExtension(); /** * The class name used by the various mapping drivers. */ - protected function getMetadataDriverClass(string $driverType): string - { - @trigger_error(sprintf('Not declaring the "%s" method in class "%s" is deprecated since Symfony 4.4. This method will be abstract in Symfony 5.0.', __METHOD__, static::class), E_USER_DEPRECATED); - - return '%'.$this->getObjectManagerElementName('metadata.'.$driverType.'.class%'); - } + abstract protected function getMetadataDriverClass(string $driverType): string; /** * Search for a manager that is declared as 'auto_mapping' = true. diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php index 352bf79bfbc7e..454c7cc0b9222 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php @@ -33,7 +33,7 @@ public function __construct(string $key, string $providerId) $this->providerId = $providerId; } - public function create(ContainerBuilder $container, $id, $config) + public function create(ContainerBuilder $container, string $id, array $config) { $container ->setDefinition($id, new ChildDefinition($this->providerId)) diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index 549ac28243ef9..99be884f34b04 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -12,27 +12,20 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; use Doctrine\Persistence\ObjectManager; -use Symfony\Component\Form\ChoiceList\ArrayChoiceList; -use Symfony\Component\Form\ChoiceList\ChoiceListInterface; -use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader; /** * Loads choices using a Doctrine object manager. * * @author Bernhard Schussek */ -class DoctrineChoiceLoader implements ChoiceLoaderInterface +class DoctrineChoiceLoader extends AbstractChoiceLoader { private $manager; private $class; private $idReader; private $objectLoader; - /** - * @var ChoiceListInterface - */ - private $choiceList; - /** * Creates a new choice loader. * @@ -47,20 +40,7 @@ public function __construct(ObjectManager $manager, string $class, IdReader $idR $classMetadata = $manager->getClassMetadata($class); if ($idReader && !$idReader->isSingleId()) { - @trigger_error(sprintf('Passing an instance of "%s" to "%s" with an entity class "%s" that has a composite id is deprecated since Symfony 4.3 and will throw an exception in 5.0.', IdReader::class, __CLASS__, $class), E_USER_DEPRECATED); - - // In Symfony 5.0 - // throw new \InvalidArgumentException(sprintf('The second argument `$idReader` of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); - } - - if ((5 > \func_num_args() || false !== func_get_arg(4)) && null === $idReader) { - $idReader = new IdReader($manager, $classMetadata); - - if ($idReader->isSingleId()) { - @trigger_error(sprintf('Not explicitly passing an instance of "%s" to "%s" when it can optimize single id entity "%s" has been deprecated in 4.3 and will not apply any optimization in 5.0.', IdReader::class, __CLASS__, $class), E_USER_DEPRECATED); - } else { - $idReader = null; - } + throw new \InvalidArgumentException(sprintf('The second argument `$idReader` of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__)); } $this->manager = $manager; @@ -72,81 +52,57 @@ public function __construct(ObjectManager $manager, string $class, IdReader $idR /** * {@inheritdoc} */ - public function loadChoiceList($value = null) + protected function loadChoices(): iterable { - if ($this->choiceList) { - return $this->choiceList; - } - - $objects = $this->objectLoader + return $this->objectLoader ? $this->objectLoader->getEntities() : $this->manager->getRepository($this->class)->findAll(); - - return $this->choiceList = new ArrayChoiceList($objects, $value); } /** - * {@inheritdoc} + * @internal to be remove in Symfony 6 */ - public function loadValuesForChoices(array $choices, $value = null) + protected function doLoadValuesForChoices(array $choices): array { - // Performance optimization - if (empty($choices)) { - return []; - } - // Optimize performance for single-field identifiers. We already // know that the IDs are used as values - $optimize = $this->idReader && (null === $value || \is_array($value) && $value[0] === $this->idReader); - // Attention: This optimization does not check choices for existence - if ($optimize && !$this->choiceList && $this->idReader->isSingleId()) { - $values = []; - + if ($this->idReader) { + trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__); // Maintain order and indices of the given objects + $values = []; foreach ($choices as $i => $object) { if ($object instanceof $this->class) { - // Make sure to convert to the right format - $values[$i] = (string) $this->idReader->getIdValue($object); + $values[$i] = $this->idReader->getIdValue($object); } } return $values; } - return $this->loadChoiceList($value)->getValuesForChoices($choices); + return parent::doLoadValuesForChoices($choices); } - /** - * {@inheritdoc} - */ - public function loadChoicesForValues(array $values, $value = null) + protected function doLoadChoicesForValues(array $values, ?callable $value): array { - // Performance optimization - // Also prevents the generation of "WHERE id IN ()" queries through the - // object loader. At least with MySQL and on the development machine - // this was tested on, no exception was thrown for such invalid - // statements, consequently no test fails when this code is removed. - // https://github.com/symfony/symfony/pull/8981#issuecomment-24230557 - if (empty($values)) { - return []; + $legacy = $this->idReader && null === $value; + + if ($legacy) { + trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__); } // Optimize performance in case we have an object loader and // a single-field identifier - $optimize = $this->idReader && (null === $value || \is_array($value) && $this->idReader === $value[0]); - - if ($optimize && !$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) { - $unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values); - $objectsById = []; + if (($legacy || \is_array($value) && $this->idReader === $value[0]) && $this->objectLoader) { $objects = []; + $objectsById = []; // Maintain order and indices from the given $values // An alternative approach to the following loop is to add the // "INDEX BY" clause to the Doctrine query in the loader, // but I'm not sure whether that's doable in a generic fashion. - foreach ($unorderedObjects as $object) { - $objectsById[(string) $this->idReader->getIdValue($object)] = $object; + foreach ($this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values) as $object) { + $objectsById[$this->idReader->getIdValue($object)] = $object; } foreach ($values as $i => $id) { @@ -158,6 +114,6 @@ public function loadChoicesForValues(array $values, $value = null) return $objects; } - return $this->loadChoiceList($value)->getChoicesForValues($values); + return parent::doLoadChoicesForValues($values, $value); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php index e36043af63cb8..8eb5a84484503 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php @@ -28,12 +28,7 @@ public function getEntities(); /** * Returns an array of entities matching the given identifiers. * - * @param string $identifier The identifier field of the object. This method - * is not applicable for fields with multiple - * identifiers. - * @param array $values The values of the identifiers - * * @return array The entities */ - public function getEntitiesByIds($identifier, array $values); + public function getEntitiesByIds(string $identifier, array $values); } diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index f56193e81062f..980c0ce89f20b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -84,14 +84,12 @@ public function isIntId(): bool * * This method assumes that the object has a single-column ID. * - * @param object $object The object - * - * @return mixed The ID value + * @return string The ID value */ - public function getIdValue($object) + public function getIdValue(object $object = null) { if (!$object) { - return null; + return ''; } if (!$this->om->contains($object)) { @@ -106,7 +104,7 @@ public function getIdValue($object) $idValue = $this->associationIdReader->getIdValue($idValue); } - return $idValue; + return (string) $idValue; } /** diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index a96a543a60a12..0d6a214c92b68 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -48,7 +48,7 @@ public function getEntities() /** * {@inheritdoc} */ - public function getEntitiesByIds($identifier, array $values) + public function getEntitiesByIds(string $identifier, array $values) { if (null !== $this->queryBuilder->getMaxResults() || null !== $this->queryBuilder->getFirstResult()) { // an offset or a limit would apply on results including the where clause with submitted id values diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 7c866f3d4185f..54c2654d7ee7d 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -43,7 +43,7 @@ public function __construct(ManagerRegistry $registry) /** * {@inheritdoc} */ - public function guessType($class, $property) + public function guessType(string $class, string $property) { if (!$ret = $this->getMetadata($class)) { return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', [], Guess::LOW_CONFIDENCE); @@ -101,7 +101,7 @@ public function guessType($class, $property) /** * {@inheritdoc} */ - public function guessRequired($class, $property) + public function guessRequired(string $class, string $property) { $classMetadatas = $this->getMetadata($class); @@ -141,7 +141,7 @@ public function guessRequired($class, $property) /** * {@inheritdoc} */ - public function guessMaxLength($class, $property) + public function guessMaxLength(string $class, string $property) { $ret = $this->getMetadata($class); if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { @@ -162,7 +162,7 @@ public function guessMaxLength($class, $property) /** * {@inheritdoc} */ - public function guessPattern($class, $property) + public function guessPattern(string $class, string $property) { $ret = $this->getMetadata($class); if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { @@ -174,7 +174,7 @@ public function guessPattern($class, $property) return null; } - protected function getMetadata($class) + protected function getMetadata(string $class) { // normalize class name $class = self::getRealClass(ltrim($class, '\\')); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 97f5facf62e74..bccb1a9ebdc3e 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -20,6 +20,7 @@ use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ChoiceList\ChoiceList; use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; use Symfony\Component\Form\Exception\RuntimeException; use Symfony\Component\Form\FormBuilderInterface; @@ -40,21 +41,19 @@ abstract class DoctrineType extends AbstractType implements ResetInterface private $idReaders = []; /** - * @var DoctrineChoiceLoader[] + * @var EntityLoaderInterface[] */ - private $choiceLoaders = []; + private $entityLoaders = []; /** * Creates the label for a choice. * * For backwards compatibility, objects are cast to strings by default. * - * @param object $choice The object - * * @internal This method is public to be usable as callback. It should not * be used in user code. */ - public static function createChoiceLabel($choice): string + public static function createChoiceLabel(object $choice): string { return (string) $choice; } @@ -66,15 +65,14 @@ public static function createChoiceLabel($choice): string * a single-column integer ID. In that case, the value of the field is * the ID of the object. That ID is also used as field name. * - * @param object $choice The object - * @param int|string $key The choice key - * @param string $value The choice value. Corresponds to the object's - * ID here. + * @param int|string $key The choice key + * @param string $value The choice value. Corresponds to the object's + * ID here. * * @internal This method is public to be usable as callback. It should not * be used in user code. */ - public static function createChoiceName($choice, $key, $value): string + public static function createChoiceName(object $choice, $key, string $value): string { return str_replace('-', '_', (string) $value); } @@ -118,44 +116,26 @@ public function configureOptions(OptionsResolver $resolver) $choiceLoader = function (Options $options) { // Unless the choices are given explicitly, load them on demand if (null === $options['choices']) { - $hash = null; - $qbParts = null; + // If there is no QueryBuilder we can safely cache + $vary = [$options['em'], $options['class']]; - // If there is no QueryBuilder we can safely cache DoctrineChoiceLoader, // also if concrete Type can return important QueryBuilder parts to generate - // hash key we go for it as well - if (!$options['query_builder'] || null !== $qbParts = $this->getQueryBuilderPartsForCachingHash($options['query_builder'])) { - $hash = CachingFactoryDecorator::generateHash([ - $options['em'], - $options['class'], - $qbParts, - ]); - - if (isset($this->choiceLoaders[$hash])) { - return $this->choiceLoaders[$hash]; - } + // hash key we go for it as well, otherwise fallback on the instance + if ($options['query_builder']) { + $vary[] = $this->getQueryBuilderPartsForCachingHash($options['query_builder']) ?? $options['query_builder']; } - if (null !== $options['query_builder']) { - $entityLoader = $this->getLoader($options['em'], $options['query_builder'], $options['class']); - } else { - $queryBuilder = $options['em']->getRepository($options['class'])->createQueryBuilder('e'); - $entityLoader = $this->getLoader($options['em'], $queryBuilder, $options['class']); - } - - $doctrineChoiceLoader = new DoctrineChoiceLoader( + return ChoiceList::loader($this, new DoctrineChoiceLoader( $options['em'], $options['class'], $options['id_reader'], - $entityLoader, - false - ); - - if (null !== $hash) { - $this->choiceLoaders[$hash] = $doctrineChoiceLoader; - } - - return $doctrineChoiceLoader; + $this->getCachedEntityLoader( + $options['em'], + $options['query_builder'] ?? $options['em']->getRepository($options['class'])->createQueryBuilder('e'), + $options['class'], + $vary + ) + ), $vary); } return null; @@ -166,7 +146,7 @@ public function configureOptions(OptionsResolver $resolver) // field name. We can only use numeric IDs as names, as we cannot // guarantee that a non-numeric ID contains a valid form name if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isIntId()) { - return [__CLASS__, 'createChoiceName']; + return ChoiceList::fieldName($this, [__CLASS__, 'createChoiceName']); } // Otherwise, an incrementing integer is used as name automatically @@ -180,7 +160,7 @@ public function configureOptions(OptionsResolver $resolver) $choiceValue = function (Options $options) { // If the entity has a single-column ID, use that ID as value if ($options['id_reader'] instanceof IdReader && $options['id_reader']->isSingleId()) { - return [$options['id_reader'], 'getIdValue']; + return ChoiceList::value($this, [$options['id_reader'], 'getIdValue'], $options['id_reader']); } // Otherwise, an incrementing integer is used as value automatically @@ -218,27 +198,13 @@ public function configureOptions(OptionsResolver $resolver) // Set the "id_reader" option via the normalizer. This option is not // supposed to be set by the user. $idReaderNormalizer = function (Options $options) { - $hash = CachingFactoryDecorator::generateHash([ - $options['em'], - $options['class'], - ]); - // The ID reader is a utility that is needed to read the object IDs // when generating the field values. The callback generating the // field values has no access to the object manager or the class // of the field, so we store that information in the reader. // The reader is cached so that two choice lists for the same class // (and hence with the same reader) can successfully be cached. - if (!isset($this->idReaders[$hash])) { - $classMetadata = $options['em']->getClassMetadata($options['class']); - $this->idReaders[$hash] = new IdReader($options['em'], $classMetadata); - } - - if ($this->idReaders[$hash]->isSingleId()) { - return $this->idReaders[$hash]; - } - - return null; + return $this->getCachedIdReader($options['em'], $options['class']); }; $resolver->setDefaults([ @@ -246,7 +212,7 @@ public function configureOptions(OptionsResolver $resolver) 'query_builder' => null, 'choices' => null, 'choice_loader' => $choiceLoader, - 'choice_label' => [__CLASS__, 'createChoiceLabel'], + 'choice_label' => ChoiceList::label($this, [__CLASS__, 'createChoiceLabel']), 'choice_name' => $choiceName, 'choice_value' => $choiceValue, 'id_reader' => null, // internal @@ -265,12 +231,11 @@ public function configureOptions(OptionsResolver $resolver) /** * Return the default loader object. * - * @param mixed $queryBuilder - * @param string $class + * @param mixed $queryBuilder * * @return EntityLoaderInterface */ - abstract public function getLoader(ObjectManager $manager, $queryBuilder, $class); + abstract public function getLoader(ObjectManager $manager, $queryBuilder, string $class); public function getParent() { @@ -279,7 +244,28 @@ public function getParent() public function reset() { - $this->choiceLoaders = []; + $this->entityLoaders = []; + } + + private function getCachedIdReader(ObjectManager $manager, string $class): ?IdReader + { + $hash = CachingFactoryDecorator::generateHash([$manager, $class]); + + if (isset($this->idReaders[$hash])) { + return $this->idReaders[$hash]; + } + + $idReader = new IdReader($manager, $manager->getClassMetadata($class)); + + // don't cache the instance for composite ids that cannot be optimized + return $this->idReaders[$hash] = $idReader->isSingleId() ? $idReader : null; + } + + private function getCachedEntityLoader(ObjectManager $manager, $queryBuilder, string $class, array $vary): EntityLoaderInterface + { + $hash = CachingFactoryDecorator::generateHash($vary); + + return $this->entityLoaders[$hash] ?? ($this->entityLoaders[$hash] = $this->getLoader($manager, $queryBuilder, $class)); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index e462d5692ca77..259f25776085a 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -47,11 +47,10 @@ public function configureOptions(OptionsResolver $resolver) * Return the default loader object. * * @param QueryBuilder $queryBuilder - * @param string $class * * @return ORMQueryBuilderLoader */ - public function getLoader(ObjectManager $manager, $queryBuilder, $class) + public function getLoader(ObjectManager $manager, $queryBuilder, string $class) { if (!$queryBuilder instanceof QueryBuilder) { throw new \TypeError(sprintf('Expected an instance of %s, but got %s.', QueryBuilder::class, \is_object($queryBuilder) ? \get_class($queryBuilder) : \gettype($queryBuilder))); diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index 27bbd984cb38f..63437920bde1d 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -62,11 +62,8 @@ public function stopQuery() /** * Logs a message. - * - * @param string $message A message to log - * @param array $params The context */ - protected function log($message, array $params) + protected function log(string $message, array $params) { $this->logger->debug($message, $params); } diff --git a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php index 2139562dedbaf..7a2ad9a8d9cd0 100644 --- a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php +++ b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php @@ -55,17 +55,13 @@ protected function resetService($name) } $manager->setProxyInitializer(\Closure::bind( function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) { - if (isset($this->normalizedIds[$normalizedId = strtolower($name)])) { // BC with DI v3.4 - $name = $this->normalizedIds[$normalizedId]; - } if (isset($this->aliases[$name])) { $name = $this->aliases[$name]; } if (isset($this->fileMap[$name])) { $wrappedInstance = $this->load($this->fileMap[$name]); } else { - $method = $this->methodMap[$name] ?? 'get'.strtr($name, $this->underscoreMap).'Service'; // BC with DI v3.4 - $wrappedInstance = $this->{$method}(false); + $wrappedInstance = $this->{$this->methodMap[$name]}(false); } $manager->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index d0e0c765bc3bd..2a70ebb2ade98 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -17,7 +17,6 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; -use Doctrine\Persistence\Mapping\ClassMetadataFactory; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; @@ -33,22 +32,11 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE { private $entityManager; private $classMetadataFactory; - private static $useDeprecatedConstants; - /** - * @param EntityManagerInterface $entityManager - */ - public function __construct($entityManager) + public function __construct(EntityManagerInterface $entityManager) { - if ($entityManager instanceof EntityManagerInterface) { - $this->entityManager = $entityManager; - } elseif ($entityManager instanceof ClassMetadataFactory) { - @trigger_error(sprintf('Injecting an instance of "%s" in "%s" is deprecated since Symfony 4.2, inject an instance of "%s" instead.', ClassMetadataFactory::class, __CLASS__, EntityManagerInterface::class), E_USER_DEPRECATED); - $this->classMetadataFactory = $entityManager; - } else { - throw new \TypeError(sprintf('$entityManager must be an instance of "%s", "%s" given.', EntityManagerInterface::class, \is_object($entityManager) ? \get_class($entityManager) : \gettype($entityManager))); - } + $this->entityManager = $entityManager; if (null === self::$useDeprecatedConstants) { self::$useDeprecatedConstants = !class_exists(Types::class); @@ -58,7 +46,7 @@ public function __construct($entityManager) /** * {@inheritdoc} */ - public function getProperties($class, array $context = []) + public function getProperties(string $class, array $context = []) { if (null === $metadata = $this->getMetadata($class)) { return null; @@ -80,7 +68,7 @@ public function getProperties($class, array $context = []) /** * {@inheritdoc} */ - public function getTypes($class, $property, array $context = []) + public function getTypes(string $class, string $property, array $context = []) { if (null === $metadata = $this->getMetadata($class)) { return null; @@ -192,7 +180,7 @@ public function getTypes($class, $property, array $context = []) /** * {@inheritdoc} */ - public function isReadable($class, $property, array $context = []) + public function isReadable(string $class, string $property, array $context = []) { return null; } @@ -200,7 +188,7 @@ public function isReadable($class, $property, array $context = []) /** * {@inheritdoc} */ - public function isWritable($class, $property, array $context = []) + public function isWritable(string $class, string $property, array $context = []) { if ( null === ($metadata = $this->getMetadata($class)) diff --git a/src/Symfony/Bridge/Doctrine/RegistryInterface.php b/src/Symfony/Bridge/Doctrine/RegistryInterface.php deleted file mode 100644 index e62b3ba49ca23..0000000000000 --- a/src/Symfony/Bridge/Doctrine/RegistryInterface.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine; - -use Doctrine\ORM\EntityManager; -use Doctrine\Persistence\ManagerRegistry; - -/** - * References Doctrine connections and entity managers. - * - * @deprecated since Symfony 4.4, use Doctrine\Persistence\ManagerRegistry instead - * - * @author Fabien Potencier - */ -interface RegistryInterface extends ManagerRegistry -{ - /** - * Gets the default entity manager name. - * - * @return string The default entity manager name - */ - public function getDefaultEntityManagerName(); - - /** - * Gets a named entity manager. - * - * @param string $name The entity manager name (null for the default one) - * - * @return EntityManager - */ - public function getEntityManager($name = null); - - /** - * Gets an array of all registered entity managers. - * - * @return array An array of EntityManager instances - */ - public function getEntityManagers(); - - /** - * Resets a named entity manager. - * - * This method is useful when an entity manager has been closed - * because of a rollbacked transaction AND when you think that - * it makes sense to get a new one to replace the closed one. - * - * Be warned that you will get a brand new entity manager as - * the existing one is not usable anymore. This means that any - * other object with a dependency on this entity manager will - * hold an obsolete reference. You can inject the registry instead - * to avoid this problem. - * - * @param string $name The entity manager name (null for the default one) - * - * @return EntityManager - */ - public function resetEntityManager($name = null); - - /** - * Resolves a registered namespace alias to the full namespace. - * - * This method looks for the alias in all registered entity managers. - * - * @param string $alias The alias - * - * @return string The full namespace - * - * @see Configuration::getEntityNamespace - */ - public function getEntityNamespace($alias); - - /** - * Gets all connection names. - * - * @return array An array of connection names - */ - public function getEntityManagerNames(); - - /** - * Gets the entity manager associated with a given class. - * - * @param string $class A Doctrine Entity class name - * - * @return EntityManager|null - */ - public function getEntityManagerForClass($class); -} diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index ef0612df3128f..e58803b397c83 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -55,7 +55,7 @@ public function __construct(Connection $conn) /** * {@inheritdoc} */ - public function loadTokenBySeries($series) + public function loadTokenBySeries(string $series) { // the alias for lastUsed works around case insensitivity in PostgreSQL $sql = 'SELECT class, username, value, lastUsed AS last_used' @@ -75,7 +75,7 @@ public function loadTokenBySeries($series) /** * {@inheritdoc} */ - public function deleteTokenBySeries($series) + public function deleteTokenBySeries(string $series) { $sql = 'DELETE FROM rememberme_token WHERE series=:series'; $paramValues = ['series' => $series]; @@ -86,7 +86,7 @@ public function deleteTokenBySeries($series) /** * {@inheritdoc} */ - public function updateToken($series, $tokenValue, \DateTime $lastUsed) + public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed) { $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed' .' WHERE series=:series'; diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index 69bbe66965cd0..65d6bed3bcd31 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -48,7 +48,7 @@ public function __construct(ManagerRegistry $registry, string $classOrAlias, str /** * {@inheritdoc} */ - public function loadUserByUsername($username) + public function loadUserByUsername(string $username) { $repository = $this->getRepository(); if (null !== $this->property) { @@ -102,7 +102,7 @@ public function refreshUser(UserInterface $user) /** * {@inheritdoc} */ - public function supportsClass($class) + public function supportsClass(string $class) { return $class === $this->getClass() || is_subclass_of($class, $this->getClass()); } diff --git a/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php b/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php index 452939fa7934a..d996f71702291 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php @@ -31,9 +31,7 @@ interface UserLoaderInterface * * This method must return null if the user is not found. * - * @param string $username The username - * * @return UserInterface|null */ - public function loadUserByUsername($username); + public function loadUserByUsername(string $username); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index 0c1a67967d118..3a7fc18058990 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -37,6 +37,7 @@ protected function setUp(): void 'getObjectManagerElementName', 'getMappingObjectDefaultName', 'getMappingResourceExtension', + 'getMetadataDriverClass', 'load', ]) ->getMock() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php index aa24cd68943dd..416d5b20bf7b1 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php @@ -2,9 +2,6 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Component\Validator\Mapping\ClassMetadata; - /** * Class BaseUser. */ @@ -40,15 +37,4 @@ public function getUsername(): string { return $this->username; } - - public static function loadValidatorMetadata(ClassMetadata $metadata): void - { - $allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; - - $metadata->addPropertyConstraint('username', new Assert\Length([ - 'min' => 2, - 'max' => 120, - 'groups' => ['Registration'], - ] + $allowEmptyString)); - } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php index 06f8674e56d66..8c0b348e3bf3a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php @@ -14,7 +14,6 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Component\Validator\Mapping\ClassMetadata; /** * @ORM\Entity @@ -37,11 +36,13 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity /** * @ORM\Column(length=20) + * @Assert\Length(min=5, allowEmptyString=true) */ public $mergedMaxLength; /** * @ORM\Column(length=20) + * @Assert\Length(min=1, max=10, allowEmptyString=true) */ public $alreadyMappedMaxLength; @@ -74,12 +75,4 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity * @Assert\DisableAutoMapping */ public $noAutoMapping; - - public static function loadValidatorMetadata(ClassMetadata $metadata): void - { - $allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; - - $metadata->addPropertyConstraint('mergedMaxLength', new Assert\Length(['min' => 5] + $allowEmptyString)); - $metadata->addPropertyConstraint('alreadyMappedMaxLength', new Assert\Length(['min' => 1, 'max' => 10] + $allowEmptyString)); - } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php index ee9597ed4975a..9ed8dcd4004bd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php @@ -19,7 +19,6 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader; -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface; @@ -101,6 +100,10 @@ protected function setUp(): void ->method('getClassMetadata') ->with($this->class) ->willReturn(new ClassMetadata($this->class)); + $this->repository->expects($this->any()) + ->method('findAll') + ->willReturn([$this->obj1, $this->obj2, $this->obj3]) + ; } public function testLoadChoiceList() @@ -187,6 +190,11 @@ public function testLoadValuesForChoicesDoesNotLoadIfEmptyChoices() $this->assertSame([], $loader->loadValuesForChoices([])); } + /** + * @group legacy + * + * @expectedDeprecation Since symfony/doctrine-bridge 5.1: Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don't pass the IdReader to "Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader" or define the "choice_value" option instead. + */ public function testLoadValuesForChoicesDoesNotLoadIfSingleIntId() { $loader = new DoctrineChoiceLoader( @@ -206,7 +214,7 @@ public function testLoadValuesForChoicesDoesNotLoadIfSingleIntId() $this->assertSame(['2'], $loader->loadValuesForChoices([$this->obj2])); } - public function testLoadValuesForChoicesLoadsIfSingleIntIdAndValueGiven() + public function testLoadValuesForChoicesDoesNotLoadIfSingleIntIdAndValueGiven() { $loader = new DoctrineChoiceLoader( $this->om, @@ -217,7 +225,7 @@ public function testLoadValuesForChoicesLoadsIfSingleIntIdAndValueGiven() $choices = [$this->obj1, $this->obj2, $this->obj3]; $value = function (\stdClass $object) { return $object->name; }; - $this->repository->expects($this->once()) + $this->repository->expects($this->never()) ->method('findAll') ->willReturn($choices); @@ -255,8 +263,7 @@ public function testLoadChoicesForValues() { $loader = new DoctrineChoiceLoader( $this->om, - $this->class, - $this->idReader + $this->class ); $choices = [$this->obj1, $this->obj2, $this->obj3]; @@ -286,7 +293,12 @@ public function testLoadChoicesForValuesDoesNotLoadIfEmptyValues() $this->assertSame([], $loader->loadChoicesForValues([])); } - public function testLoadChoicesForValuesLoadsOnlyChoicesIfSingleIntId() + /** + * @group legacy + * + * @expectedDeprecation Not defining explicitly the IdReader as value callback when query can be optimized has been deprecated in 5.1. Don't pass the IdReader to "Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader" or define the choice_value instead. + */ + public function legacyTestLoadChoicesForValuesLoadsOnlyChoicesIfValueUseIdReader() { $loader = new DoctrineChoiceLoader( $this->om, @@ -322,6 +334,42 @@ public function testLoadChoicesForValuesLoadsOnlyChoicesIfSingleIntId() )); } + public function testLoadChoicesForValuesLoadsOnlyChoicesIfValueUseIdReader() + { + $loader = new DoctrineChoiceLoader( + $this->om, + $this->class, + $this->idReader, + $this->objectLoader + ); + + $choices = [$this->obj2, $this->obj3]; + + $this->idReader->expects($this->any()) + ->method('getIdField') + ->willReturn('idField'); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->objectLoader->expects($this->once()) + ->method('getEntitiesByIds') + ->with('idField', [4 => '3', 7 => '2']) + ->willReturn($choices); + + $this->idReader->expects($this->any()) + ->method('getIdValue') + ->willReturnMap([ + [$this->obj2, '2'], + [$this->obj3, '3'], + ]); + + $this->assertSame( + [4 => $this->obj3, 7 => $this->obj2], + $loader->loadChoicesForValues([4 => '3', 7 => '2'], [$this->idReader, 'getIdValue'] + )); + } + public function testLoadChoicesForValuesLoadsAllIfSingleIntIdAndValueGiven() { $loader = new DoctrineChoiceLoader( @@ -377,87 +425,17 @@ public function testLoadChoicesForValuesLoadsOnlyChoicesIfValueIsIdReader() $this->assertSame([$this->obj2], $loader->loadChoicesForValues(['2'], $value)); } - /** - * @group legacy - * - * @expectedDeprecation Not explicitly passing an instance of "Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader" to "Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader" when it can optimize single id entity "%s" has been deprecated in 4.3 and will not apply any optimization in 5.0. - */ - public function testLoaderWithoutIdReaderCanBeOptimized() - { - $obj1 = new SingleIntIdEntity('1', 'one'); - $obj2 = new SingleIntIdEntity('2', 'two'); - - $metadata = $this->createMock(ClassMetadata::class); - $metadata->expects($this->once()) - ->method('getIdentifierFieldNames') - ->willReturn(['idField']) - ; - $metadata->expects($this->any()) - ->method('getIdentifierValues') - ->willReturnCallback(function ($obj) use ($obj1, $obj2) { - if ($obj === $obj1) { - return ['idField' => '1']; - } - if ($obj === $obj2) { - return ['idField' => '2']; - } - - return null; - }) - ; - - $this->om = $this->createMock(ObjectManager::class); - $this->om->expects($this->once()) - ->method('getClassMetadata') - ->with(SingleIntIdEntity::class) - ->willReturn($metadata) - ; - $this->om->expects($this->any()) - ->method('contains') - ->with($this->isInstanceOf(SingleIntIdEntity::class)) - ->willReturn(true) - ; - - $loader = new DoctrineChoiceLoader( - $this->om, - SingleIntIdEntity::class, - null, - $this->objectLoader - ); - - $choices = [$obj1, $obj2]; - - $this->repository->expects($this->never()) - ->method('findAll'); - - $this->objectLoader->expects($this->once()) - ->method('getEntitiesByIds') - ->with('idField', ['1']) - ->willReturn($choices); - - $this->assertSame([$obj1], $loader->loadChoicesForValues(['1'])); - } - - /** - * @group legacy - * - * @deprecationMessage Passing an instance of "Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader" to "Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader" with an entity class "stdClass" that has a composite id is deprecated since Symfony 4.3 and will throw an exception in 5.0. - */ public function testPassingIdReaderWithoutSingleIdEntity() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The second argument `$idReader` of "Symfony\\Bridge\\Doctrine\\Form\\ChoiceList\\DoctrineChoiceLoader::__construct" must be null when the query cannot be optimized because of composite id fields.'); + $idReader = $this->createMock(IdReader::class); $idReader->expects($this->once()) ->method('isSingleId') ->willReturn(false) ; - $loader = new DoctrineChoiceLoader( - $this->om, - $this->class, - $idReader, - $this->objectLoader - ); - - $this->assertInstanceOf(DoctrineChoiceLoader::class, $loader); + new DoctrineChoiceLoader($this->om, $this->class, $idReader, $this->objectLoader); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index ec8f7933f9a9b..ec51c708aec03 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -1205,13 +1205,13 @@ public function testLoaderCaching() 'property3' => 2, ]); - $choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader'); - $choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader'); - $choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader'); + $choiceList1 = $form->get('property1')->getConfig()->getAttribute('choice_list'); + $choiceList2 = $form->get('property2')->getConfig()->getAttribute('choice_list'); + $choiceList3 = $form->get('property3')->getConfig()->getAttribute('choice_list'); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); - $this->assertSame($choiceLoader1, $choiceLoader2); - $this->assertSame($choiceLoader1, $choiceLoader3); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\LazyChoiceList', $choiceList1); + $this->assertSame($choiceList1, $choiceList2); + $this->assertSame($choiceList1, $choiceList3); } public function testLoaderCachingWithParameters() @@ -1265,13 +1265,13 @@ public function testLoaderCachingWithParameters() 'property3' => 2, ]); - $choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader'); - $choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader'); - $choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader'); + $choiceList1 = $form->get('property1')->getConfig()->getAttribute('choice_list'); + $choiceList2 = $form->get('property2')->getConfig()->getAttribute('choice_list'); + $choiceList3 = $form->get('property3')->getConfig()->getAttribute('choice_list'); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); - $this->assertSame($choiceLoader1, $choiceLoader2); - $this->assertSame($choiceLoader1, $choiceLoader3); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\LazyChoiceList', $choiceList1); + $this->assertSame($choiceList1, $choiceList2); + $this->assertSame($choiceList1, $choiceList3); } protected function createRegistryMock($name, $em) diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 3df655df12a28..c7dbb08a7b1ac 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -28,7 +28,7 @@ */ class DoctrineExtractorTest extends TestCase { - private function createExtractor(bool $legacy = false) + private function createExtractor() { $config = Setup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); @@ -38,20 +38,10 @@ private function createExtractor(bool $legacy = false) $entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('custom_foo', 'foo'); } - return new DoctrineExtractor($legacy ? $entityManager->getMetadataFactory() : $entityManager); + return new DoctrineExtractor($entityManager); } public function testGetProperties() - { - $this->doTestGetProperties(false); - } - - public function testLegacyGetProperties() - { - $this->doTestGetProperties(true); - } - - private function doTestGetProperties(bool $legacy) { // Fields $expected = [ @@ -86,21 +76,11 @@ private function doTestGetProperties(bool $legacy) $this->assertEquals( $expected, - $this->createExtractor($legacy)->getProperties(!class_exists(Types::class) ? 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy' : DoctrineDummy210::class) + $this->createExtractor()->getProperties(!class_exists(Types::class) ? 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy' : DoctrineDummy210::class) ); } public function testTestGetPropertiesWithEmbedded() - { - $this->doTestGetPropertiesWithEmbedded(false); - } - - public function testLegacyTestGetPropertiesWithEmbedded() - { - $this->doTestGetPropertiesWithEmbedded(true); - } - - private function doTestGetPropertiesWithEmbedded(bool $legacy) { if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); @@ -111,7 +91,7 @@ private function doTestGetPropertiesWithEmbedded(bool $legacy) 'id', 'embedded', ], - $this->createExtractor($legacy)->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded') + $this->createExtractor()->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded') ); } @@ -120,33 +100,10 @@ private function doTestGetPropertiesWithEmbedded(bool $legacy) */ public function testExtract($property, array $type = null) { - $this->doTestExtract(false, $property, $type); - } - - /** - * @dataProvider typesProvider - */ - public function testLegacyExtract($property, array $type = null) - { - $this->doTestExtract(true, $property, $type); - } - - private function doTestExtract(bool $legacy, $property, array $type = null) - { - $this->assertEquals($type, $this->createExtractor($legacy)->getTypes(!class_exists(Types::class) ? 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy' : DoctrineDummy210::class, $property, [])); + $this->assertEquals($type, $this->createExtractor()->getTypes(!class_exists(Types::class) ? 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy' : DoctrineDummy210::class, $property, [])); } public function testExtractWithEmbedded() - { - $this->doTestExtractWithEmbedded(false); - } - - public function testLegacyExtractWithEmbedded() - { - $this->doTestExtractWithEmbedded(true); - } - - private function doTestExtractWithEmbedded(bool $legacy) { if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); @@ -158,7 +115,7 @@ private function doTestExtractWithEmbedded(bool $legacy) 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable' )]; - $actualTypes = $this->createExtractor($legacy)->getTypes( + $actualTypes = $this->createExtractor()->getTypes( 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded', 'embedded', [] @@ -229,32 +186,12 @@ public function typesProvider() public function testGetPropertiesCatchException() { - $this->doTestGetPropertiesCatchException(false); - } - - public function testLegacyGetPropertiesCatchException() - { - $this->doTestGetPropertiesCatchException(true); - } - - private function doTestGetPropertiesCatchException(bool $legacy) - { - $this->assertNull($this->createExtractor($legacy)->getProperties('Not\Exist')); + $this->assertNull($this->createExtractor()->getProperties('Not\Exist')); } public function testGetTypesCatchException() { - return $this->doTestGetTypesCatchException(false); - } - - public function testLegacyGetTypesCatchException() - { - return $this->doTestGetTypesCatchException(true); - } - - private function doTestGetTypesCatchException(bool $legacy) - { - $this->assertNull($this->createExtractor($legacy)->getTypes('Not\Exist', 'baz')); + $this->assertNull($this->createExtractor()->getTypes('Not\Exist', 'baz')); } public function testGeneratedValueNotWritable() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml b/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml index ddb8a13bc1fcc..40b7a138d437b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml +++ b/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml @@ -9,6 +9,12 @@ + + + + + + diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index db691401cef8d..35a90044f7bab 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -46,7 +46,6 @@ protected function setUp(): void public function testLoadClassMetadata() { $validator = Validation::createValidatorBuilder() - ->addMethodMapping('loadValidatorMetadata') ->enableAnnotationMapping() ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}')) ->getValidator() @@ -152,7 +151,6 @@ public function testLoadClassMetadata() public function testFieldMappingsConfiguration() { $validator = Validation::createValidatorBuilder() - ->addMethodMapping('loadValidatorMetadata') ->enableAnnotationMapping() ->addXmlMappings([__DIR__.'/../Resources/validator/BaseUser.xml']) ->addLoader( diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php index 659bd8569759d..28d5fccc7459c 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php @@ -28,7 +28,7 @@ public function __construct(ManagerRegistry $registry) $this->registry = $registry; } - public function initialize($object) + public function initialize(object $object) { $manager = $this->registry->getManagerForClass(\get_class($object)); if (null !== $manager) { diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 64b21b4262040..26eeb9eb491ab 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^7.1.3", + "php": "^7.2.5", "doctrine/event-manager": "~1.0", "doctrine/persistence": "^1.3", "symfony/polyfill-ctype": "~1.8", @@ -24,20 +24,20 @@ "symfony/service-contracts": "^1.1|^2" }, "require-dev": { - "symfony/stopwatch": "^3.4|^4.0|^5.0", - "symfony/config": "^4.2|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/form": "^4.4|^5.0", - "symfony/http-kernel": "^4.3.7", + "symfony/stopwatch": "^4.4|^5.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/form": "^5.1", + "symfony/http-kernel": "^5.0", "symfony/messenger": "^4.4|^5.0", - "symfony/property-access": "^3.4|^4.0|^5.0", - "symfony/property-info": "^3.4|^4.0|^5.0", - "symfony/proxy-manager-bridge": "^3.4|^4.0|^5.0", - "symfony/security-core": "^4.4|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/validator": "^4.4.2|^5.0.2", - "symfony/var-dumper": "^3.4|^4.0|^5.0", - "symfony/translation": "^3.4|^4.0|^5.0", + "symfony/property-access": "^4.4|^5.0", + "symfony/property-info": "^5.0", + "symfony/proxy-manager-bridge": "^4.4|^5.0", + "symfony/security-core": "^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/validator": "^5.0.2", + "symfony/translation": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0", "doctrine/annotations": "~1.7", "doctrine/cache": "~1.6", "doctrine/collections": "~1.0", @@ -47,13 +47,15 @@ "doctrine/reflection": "~1.0" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/dependency-injection": "<3.4", - "symfony/form": "<4.4", - "symfony/http-kernel": "<4.3.7", - "symfony/messenger": "<4.3", - "symfony/security-core": "<4.4", - "symfony/validator": "<4.4.2|<5.0.2,>=5.0" + "phpunit/phpunit": "<5.4.3", + "symfony/dependency-injection": "<4.4", + "symfony/form": "<5.1", + "symfony/http-kernel": "<5", + "symfony/messenger": "<4.4", + "symfony/property-info": "<5", + "symfony/security-bundle": "<5", + "symfony/security-core": "<5", + "symfony/validator": "<5.0.2" }, "suggest": { "symfony/form": "", @@ -72,7 +74,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } } } diff --git a/src/Symfony/Bridge/Monolog/CHANGELOG.md b/src/Symfony/Bridge/Monolog/CHANGELOG.md index 9deec49664ae3..2a4d31a2ab340 100644 --- a/src/Symfony/Bridge/Monolog/CHANGELOG.md +++ b/src/Symfony/Bridge/Monolog/CHANGELOG.md @@ -1,6 +1,16 @@ CHANGELOG ========= +5.1.0 +----- + * Added `MailerHandler` + +5.0.0 +----- + + * The methods `DebugProcessor::getLogs()`, `DebugProcessor::countErrors()`, `Logger::getLogs()` and `Logger::countErrors()` have a new `$request` argument. + * Added support for Monolog 2. + 4.4.0 ----- diff --git a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php index 4d722c46ecfcf..e7049c4b614cc 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php @@ -13,14 +13,14 @@ use Monolog\Handler\ChromePHPHandler as BaseChromePhpHandler; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; /** * ChromePhpHandler. * * @author Christophe Coevoet * - * @final since Symfony 4.3 + * @final */ class ChromePhpHandler extends BaseChromePhpHandler { @@ -34,7 +34,7 @@ class ChromePhpHandler extends BaseChromePhpHandler /** * Adds the headers to the response once it's created. */ - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { if (!$event->isMasterRequest()) { return; @@ -57,7 +57,7 @@ public function onKernelResponse(FilterResponseEvent $event) /** * {@inheritdoc} */ - protected function sendHeader($header, $content) + protected function sendHeader($header, $content): void { if (!self::$sendHeaders) { return; @@ -72,10 +72,8 @@ protected function sendHeader($header, $content) /** * Override default behavior since we check it in onKernelResponse. - * - * @return bool */ - protected function headersAccepted() + protected function headersAccepted(): bool { return true; } diff --git a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php index 8dd4808fe8903..64b3d65fa5adf 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php @@ -74,20 +74,16 @@ public function __construct(OutputInterface $output = null, bool $bubble = true, /** * {@inheritdoc} - * - * @return bool */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->updateLevel() && parent::isHandling($record); } /** * {@inheritdoc} - * - * @return bool */ - public function handle(array $record) + public function handle(array $record): bool { // we have to update the logging level each time because the verbosity of the // console output might have changed in the meantime (it is not immutable) @@ -105,7 +101,7 @@ public function setOutput(OutputInterface $output) /** * Disables the output. */ - public function close() + public function close(): void { $this->output = null; @@ -147,10 +143,8 @@ public static function getSubscribedEvents() /** * {@inheritdoc} - * - * @return void */ - protected function write(array $record) + protected function write(array $record): void { // at this point we've determined for sure that we want to output the record, so use the output's own verbosity $this->output->write((string) $record['formatted'], false, $this->output->getVerbosity()); @@ -158,10 +152,8 @@ protected function write(array $record) /** * {@inheritdoc} - * - * @return FormatterInterface */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { if (!class_exists(CliDumper::class)) { return new LineFormatter(); diff --git a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php index 062ca2103bb7e..cc0b19774c6c4 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php +++ b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/HttpCodeActivationStrategy.php @@ -45,10 +45,7 @@ public function __construct(RequestStack $requestStack, array $exclusions, $acti $this->exclusions = $exclusions; } - /** - * @return bool - */ - public function isHandlerActivated(array $record) + public function isHandlerActivated(array $record): bool { $isActivated = parent::isHandlerActivated($record); diff --git a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php index 08975d0c64b8d..4bea3cc8a8cdc 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php +++ b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php @@ -34,10 +34,7 @@ public function __construct(RequestStack $requestStack, array $excludedUrls, $ac $this->blacklist = '{('.implode('|', $excludedUrls).')}i'; } - /** - * @return bool - */ - public function isHandlerActivated(array $record) + public function isHandlerActivated(array $record): bool { $isActivated = parent::isHandlerActivated($record); diff --git a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php index f006118223cba..0a9a6965db4a9 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php @@ -13,14 +13,14 @@ use Monolog\Handler\FirePHPHandler as BaseFirePHPHandler; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; /** * FirePHPHandler. * * @author Jordi Boggiano * - * @final since Symfony 4.3 + * @final */ class FirePHPHandler extends BaseFirePHPHandler { @@ -34,7 +34,7 @@ class FirePHPHandler extends BaseFirePHPHandler /** * Adds the headers to the response once it's created. */ - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { if (!$event->isMasterRequest()) { return; @@ -59,7 +59,7 @@ public function onKernelResponse(FilterResponseEvent $event) /** * {@inheritdoc} */ - protected function sendHeader($header, $content) + protected function sendHeader($header, $content): void { if (!self::$sendHeaders) { return; @@ -74,10 +74,8 @@ protected function sendHeader($header, $content) /** * Override default behavior since we check the user agent in onKernelResponse. - * - * @return bool */ - protected function headersAccepted() + protected function headersAccepted(): bool { return true; } diff --git a/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php new file mode 100644 index 0000000000000..1970d7085f0af --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Handler/MailerHandler.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\HtmlFormatter; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Logger; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Email; + +/** + * @author Alexander Borisov + */ +class MailerHandler extends AbstractProcessingHandler +{ + private $mailer; + + private $messageTemplate; + + /** + * @param callable|Email $messageTemplate + */ + public function __construct(MailerInterface $mailer, $messageTemplate, int $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->messageTemplate = $messageTemplate; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records): void + { + $messages = []; + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * {@inheritdoc} + */ + protected function write(array $record): void + { + $this->send((string) $record['formatted'], [$record]); + } + + /** + * Send a mail with the given content. + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + */ + protected function send(string $content, array $records) + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Message subject. + * + * @param string $format The format of the subject + */ + protected function getSubjectFormatter(string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Message to be sent. + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + */ + protected function buildMessage(string $content, array $records): Email + { + $message = null; + if ($this->messageTemplate instanceof Email) { + $message = clone $this->messageTemplate; + } elseif (\is_callable($this->messageTemplate)) { + $message = \call_user_func($this->messageTemplate, $content, $records); + if (!$message instanceof Email) { + throw new \InvalidArgumentException(sprintf('Could not resolve message from a callable. Instance of "%s" is expected', Email::class)); + } + } else { + throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it'); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->subject($subjectFormatter->format($this->getHighestRecord($records))); + } + + if ($this->getFormatter() instanceof HtmlFormatter) { + if ($message->getHtmlCharset()) { + $message->html($content, $message->getHtmlCharset()); + } else { + $message->html($content); + } + } else { + if ($message->getTextCharset()) { + $message->text($content, $message->getTextCharset()); + } else { + $message->text($content); + } + } + + return $message; + } + + protected function getHighestRecord(array $records): array + { + $highestRecord = null; + foreach ($records as $record) { + if (null === $highestRecord || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } +} diff --git a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php new file mode 100644 index 0000000000000..6152dece8f717 --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Handler; + +use Monolog\Handler\AbstractHandler; +use Monolog\Logger; +use Symfony\Component\Notifier\Notification\Notification; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\NotifierInterface; + +/** + * Uses Notifier as a log handler. + * + * @author Fabien Potencier + */ +class NotifierHandler extends AbstractHandler +{ + private $notifier; + + public function __construct(NotifierInterface $notifier, int $level = Logger::ERROR, bool $bubble = true) + { + $this->notifier = $notifier; + + parent::__construct($level < Logger::ERROR ? Logger::ERROR : $level, $bubble); + } + + public function handle(array $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + $this->notify([$record]); + + return !$this->bubble; + } + + public function handleBatch(array $records): void + { + if ($records = array_filter($records, [$this, 'isHandling'])) { + $this->notify($records); + } + } + + private function notify(array $records): void + { + $record = $this->getHighestRecord($records); + if (($record['context']['exception'] ?? null) instanceof \Throwable) { + $notification = Notification::fromThrowable($record['context']['exception']); + } else { + $notification = new Notification($record['message']); + } + + $notification->importanceFromLogLevelName(Logger::getLevelName($record['level'])); + + $this->notifier->send($notification, ...$this->notifier->getAdminRecipients()); + } + + private function getHighestRecord(array $records) + { + $highestRecord = null; + foreach ($records as $record) { + if (null === $highestRecord || $highestRecord['level'] < $record['level']) { + $highestRecord = $record; + } + } + + return $highestRecord; + } +} diff --git a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php index 28582d31725a3..4016d402b9634 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php @@ -12,14 +12,43 @@ namespace Symfony\Bridge\Monolog\Handler; use Monolog\Formatter\FormatterInterface; -use Monolog\Handler\AbstractHandler; +use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Handler\FormattableHandlerTrait; use Monolog\Logger; use Symfony\Bridge\Monolog\Formatter\VarDumperFormatter; +if (trait_exists(FormattableHandlerTrait::class)) { + class ServerLogHandler extends AbstractProcessingHandler + { + use ServerLogHandlerTrait; + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new VarDumperFormatter(); + } + } +} else { + class ServerLogHandler extends AbstractProcessingHandler + { + use ServerLogHandlerTrait; + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new VarDumperFormatter(); + } + } +} + /** * @author Grégoire Pineau */ -class ServerLogHandler extends AbstractHandler +trait ServerLogHandlerTrait { private $host; private $context; @@ -39,10 +68,8 @@ public function __construct(string $host, int $level = Logger::DEBUG, bool $bubb /** * {@inheritdoc} - * - * @return bool */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; @@ -58,6 +85,11 @@ public function handle(array $record) restore_error_handler(); } + return parent::handle($record); + } + + protected function write(array $record): void + { $recordFormatted = $this->formatRecord($record); set_error_handler(self::class.'::nullErrorHandler'); @@ -74,16 +106,12 @@ public function handle(array $record) } finally { restore_error_handler(); } - - return false === $this->bubble; } /** * {@inheritdoc} - * - * @return FormatterInterface */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new VarDumperFormatter(); } @@ -105,13 +133,7 @@ private function createSocket() private function formatRecord(array $record): string { - if ($this->processors) { - foreach ($this->processors as $processor) { - $record = $processor($record); - } - } - - $recordFormatted = $this->getFormatter()->format($record); + $recordFormatted = $record['formatted']; foreach (['log_uuid', 'uuid', 'uid'] as $key) { if (isset($record['extra'][$key])) { diff --git a/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php index 93f2f72e6457a..f255f0a83ed16 100644 --- a/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php @@ -13,14 +13,14 @@ use Monolog\Handler\SwiftMailerHandler as BaseSwiftMailerHandler; use Symfony\Component\Console\Event\ConsoleTerminateEvent; -use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Event\TerminateEvent; /** * Extended SwiftMailerHandler that flushes mail queue if necessary. * * @author Philipp Kräutli * - * @final since Symfony 4.3 + * @final */ class SwiftMailerHandler extends BaseSwiftMailerHandler { @@ -36,7 +36,7 @@ public function setTransport(\Swift_Transport $transport) /** * After the kernel has been terminated we will always flush messages. */ - public function onKernelTerminate(PostResponseEvent $event) + public function onKernelTerminate(TerminateEvent $event) { $this->instantFlush = true; } @@ -52,7 +52,7 @@ public function onCliTerminate(ConsoleTerminateEvent $event) /** * {@inheritdoc} */ - protected function send($content, array $records) + protected function send($content, array $records): void { parent::send($content, $records); @@ -64,7 +64,7 @@ protected function send($content, array $records) /** * {@inheritdoc} */ - public function reset() + public function reset(): void { $this->flushMemorySpool(); } diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index a4c25392ab9cc..4643f5b6d7598 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -24,17 +24,11 @@ class Logger extends BaseLogger implements DebugLoggerInterface, ResetInterface { /** * {@inheritdoc} - * - * @param Request|null $request */ - public function getLogs(/* Request $request = null */) + public function getLogs(Request $request = null) { - if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) { - @trigger_error(sprintf('The "%s()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); - } - if ($logger = $this->getDebugLogger()) { - return $logger->getLogs(...\func_get_args()); + return $logger->getLogs($request); } return []; @@ -42,17 +36,11 @@ public function getLogs(/* Request $request = null */) /** * {@inheritdoc} - * - * @param Request|null $request */ - public function countErrors(/* Request $request = null */) + public function countErrors(Request $request = null) { - if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) { - @trigger_error(sprintf('The "%s()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); - } - if ($logger = $this->getDebugLogger()) { - return $logger->countErrors(...\func_get_args()); + return $logger->countErrors($request); } return 0; @@ -71,7 +59,7 @@ public function clear() /** * {@inheritdoc} */ - public function reset() + public function reset(): void { $this->clear(); diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index 6addf02638dc8..7ba3f4668a18c 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -58,16 +58,10 @@ public function __invoke(array $record) /** * {@inheritdoc} - * - * @param Request|null $request */ - public function getLogs(/* Request $request = null */) + public function getLogs(Request $request = null) { - if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) { - @trigger_error(sprintf('The "%s()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); - } - - if (1 <= \func_num_args() && null !== $request = func_get_arg(0)) { + if (null !== $request) { return $this->records[spl_object_hash($request)] ?? []; } @@ -80,16 +74,10 @@ public function getLogs(/* Request $request = null */) /** * {@inheritdoc} - * - * @param Request|null $request */ - public function countErrors(/* Request $request = null */) + public function countErrors(Request $request = null) { - if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) { - @trigger_error(sprintf('The "%s()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED); - } - - if (1 <= \func_num_args() && null !== $request = func_get_arg(0)) { + if (null !== $request) { return $this->errorCount[spl_object_hash($request)] ?? 0; } diff --git a/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php b/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php index 09507b55e7fb2..23b95d9b8512c 100644 --- a/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/RouteProcessor.php @@ -13,7 +13,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Contracts\Service\ResetInterface; @@ -22,7 +22,7 @@ * * @author Piotr Stankowski * - * @final since Symfony 4.4 + * @final */ class RouteProcessor implements EventSubscriberInterface, ResetInterface { @@ -35,7 +35,7 @@ public function __construct(bool $includeParams = true) $this->reset(); } - public function __invoke(array $records) + public function __invoke(array $records): array { if ($this->routeData && !isset($records['extra']['requests'])) { $records['extra']['requests'] = array_values($this->routeData); @@ -49,7 +49,7 @@ public function reset() $this->routeData = []; } - public function addRouteData(GetResponseEvent $event) + public function addRouteData(RequestEvent $event) { if ($event->isMasterRequest()) { $this->reset(); @@ -78,7 +78,7 @@ public function removeRouteData(FinishRequestEvent $event) unset($this->routeData[$requestId]); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => ['addRouteData', 1], diff --git a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php index 7613d01361962..78d8dd3249c6d 100644 --- a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php @@ -31,11 +31,7 @@ public function __invoke(array $records) { $records['extra']['token'] = null; if (null !== $token = $this->tokenStorage->getToken()) { - if (method_exists($token, 'getRoleNames')) { - $roles = $token->getRoleNames(); - } else { - $roles = array_map(function ($role) { return $role->getRole(); }, $token->getRoles(false)); - } + $roles = $token->getRoleNames(); $records['extra']['token'] = [ 'username' => $token->getUsername(), diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index 71bf71a816327..98ec10ca6dcb4 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -13,7 +13,7 @@ use Monolog\Processor\WebProcessor as BaseWebProcessor; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; /** @@ -21,7 +21,7 @@ * * @author Jordi Boggiano * - * @final since Symfony 4.3 + * @final */ class WebProcessor extends BaseWebProcessor implements EventSubscriberInterface { @@ -31,7 +31,7 @@ public function __construct(array $extraFields = null) parent::__construct([], $extraFields); } - public function onKernelRequest(GetResponseEvent $event) + public function onKernelRequest(RequestEvent $event) { if ($event->isMasterRequest()) { $this->serverData = $event->getRequest()->server->all(); @@ -39,7 +39,7 @@ public function onKernelRequest(GetResponseEvent $event) } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => ['onKernelRequest', 4096], diff --git a/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php b/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php index 31c62e3e75591..e258c7942a20a 100644 --- a/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php +++ b/src/Symfony/Bridge/Monolog/Tests/ClassThatInheritLogger.php @@ -12,16 +12,17 @@ namespace Symfony\Bridge\Monolog\Tests; use Symfony\Bridge\Monolog\Logger; +use Symfony\Component\HttpFoundation\Request; class ClassThatInheritLogger extends Logger { - public function getLogs(): array + public function getLogs(Request $request = null): array { - return parent::getLogs(); + return parent::getLogs($request); } - public function countErrors(): int + public function countErrors(Request $request = null): int { - return parent::countErrors(); + return parent::countErrors($request); } } diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php new file mode 100644 index 0000000000000..24aaa6b95cdd9 --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Tests\Handler; + +use Monolog\Formatter\HtmlFormatter; +use Monolog\Formatter\LineFormatter; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Monolog\Handler\MailerHandler; +use Symfony\Bridge\Monolog\Logger; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Email; + +class MailerHandlerTest extends TestCase +{ + /** @var MockObject|MailerInterface */ + private $mailer = null; + + protected function setUp(): void + { + $this->mailer = $this->createMock(MailerInterface::class); + } + + public function testHandle() + { + $handler = new MailerHandler($this->mailer, (new Email())->subject('Alert: %level_name% %message%')); + $handler->setFormatter(new LineFormatter()); + $this->mailer + ->expects($this->once()) + ->method('send') + ->with($this->callback(function (Email $email) { + return 'Alert: WARNING message' === $email->getSubject() && null === $email->getHtmlBody(); + })) + ; + $handler->handle($this->getRecord(Logger::WARNING, 'message')); + } + + public function testHandleBatch() + { + $handler = new MailerHandler($this->mailer, (new Email())->subject('Alert: %level_name% %message%')); + $handler->setFormatter(new LineFormatter()); + $this->mailer + ->expects($this->once()) + ->method('send') + ->with($this->callback(function (Email $email) { + return 'Alert: ERROR error' === $email->getSubject() && null === $email->getHtmlBody(); + })) + ; + $handler->handleBatch($this->getMultipleRecords()); + } + + public function testMessageCreationIsLazyWhenUsingCallback() + { + $this->mailer + ->expects($this->never()) + ->method('send') + ; + + $callback = function () { + throw new \RuntimeException('Email creation callback should not have been called in this test'); + }; + $handler = new MailerHandler($this->mailer, $callback, Logger::ALERT); + + $records = [ + $this->getRecord(Logger::DEBUG), + $this->getRecord(Logger::INFO), + ]; + $handler->handleBatch($records); + } + + public function testHtmlContent() + { + $handler = new MailerHandler($this->mailer, (new Email())->subject('Alert: %level_name% %message%')); + $handler->setFormatter(new HtmlFormatter()); + $this->mailer + ->expects($this->once()) + ->method('send') + ->with($this->callback(function (Email $email) { + return 'Alert: WARNING message' === $email->getSubject() && null === $email->getTextBody(); + })) + ; + $handler->handle($this->getRecord(Logger::WARNING, 'message')); + } + + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = []) + { + return [ + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), + 'extra' => [], + ]; + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return [ + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ]; + } +} diff --git a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php index 2b2c7a3763a72..f170c526b8f74 100644 --- a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php @@ -125,15 +125,15 @@ public function testReset() } } - /** - * @group legacy - * @expectedDeprecation The "Symfony\Bridge\Monolog\Logger::getLogs()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2. - * @expectedDeprecation The "Symfony\Bridge\Monolog\Logger::countErrors()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2. - */ - public function testInheritedClassWithoutArgument() + public function testInheritedClassCallGetLogsWithoutArgument() { $loggerChild = new ClassThatInheritLogger('test'); - $loggerChild->getLogs(); - $loggerChild->countErrors(); + $this->assertSame([], $loggerChild->getLogs()); + } + + public function testInheritedClassCallCountErrorsWithoutArgument() + { + $loggerChild = new ClassThatInheritLogger('test'); + $this->assertEquals(0, $loggerChild->countErrors()); } } diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php b/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php index 1f15bd9f764b2..bc87c724c9d31 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/ClassThatInheritDebugProcessor.php @@ -12,16 +12,17 @@ namespace Symfony\Bridge\Monolog\Tests\Processor; use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Symfony\Component\HttpFoundation\Request; class ClassThatInheritDebugProcessor extends DebugProcessor { - public function getLogs(): array + public function getLogs(Request $request = null): array { - return parent::getLogs(); + return parent::getLogs($request); } - public function countErrors(): int + public function countErrors(Request $request = null): int { - return parent::countErrors(); + return parent::countErrors($request); } } diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php index 4ac41c978ec4e..6adec38a0c7f0 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php @@ -87,16 +87,16 @@ public function testWithRequestStack() $this->assertSame(0, $processor->countErrors(new Request())); } - /** - * @group legacy - * @expectedDeprecation The "Symfony\Bridge\Monolog\Processor\DebugProcessor::getLogs()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2. - * @expectedDeprecation The "Symfony\Bridge\Monolog\Processor\DebugProcessor::countErrors()" method will have a new "Request $request = null" argument in version 5.0, not defining it is deprecated since Symfony 4.2. - */ - public function testInheritedClassWithoutArgument() + public function testInheritedClassCallGetLogsWithoutArgument() + { + $debugProcessorChild = new ClassThatInheritDebugProcessor(); + $this->assertSame([], $debugProcessorChild->getLogs()); + } + + public function testInheritedClassCallCountErrorsWithoutArgument() { $debugProcessorChild = new ClassThatInheritDebugProcessor(); - $debugProcessorChild->getLogs(); - $debugProcessorChild->countErrors(); + $this->assertEquals(0, $debugProcessorChild->countErrors()); } private function getRecord($level = Logger::WARNING, $message = 'test'): array diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 0325ed5447a8d..e3c0874f929ba 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -16,20 +16,22 @@ } ], "require": { - "php": "^7.1.3", - "monolog/monolog": "^1.25.1", + "php": "^7.2.5", + "monolog/monolog": "^1.25.1|^2", "symfony/service-contracts": "^1.1|^2", - "symfony/http-kernel": "^4.3" + "symfony/http-kernel": "^4.4|^5.0" }, "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0", + "symfony/console": "^4.4|^5.0", "symfony/http-client": "^4.4|^5.0", - "symfony/security-core": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^3.4|^4.0|^5.0" + "symfony/security-core": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0", + "symfony/mailer": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" }, "conflict": { - "symfony/console": "<3.4", - "symfony/http-foundation": "<3.4" + "symfony/console": "<4.4", + "symfony/http-foundation": "<4.4" }, "suggest": { "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", @@ -45,7 +47,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } } } diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index b85364ad7dd7f..b3d20b6adf2de 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -1,6 +1,18 @@ CHANGELOG ========= +5.1.0 +----- + + * ignore verbosity settings when the build fails because of deprecations + * added per-group verbosity + * added `ExpectDeprecationTrait` to be able to define an expected deprecation from inside a test + +5.0.0 +----- + + * removed `weak_vendor` mode, use `max[self]=0` instead + 4.4.0 ----- diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index cbe405fa22bd0..72f9c7f76f954 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -15,6 +15,7 @@ use PHPUnit\Util\ErrorHandler; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Configuration; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation; +use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\DeprecationGroup; use Symfony\Component\ErrorHandler\DebugClassLoader; /** @@ -24,35 +25,26 @@ */ class DeprecationErrorHandler { - /** - * @deprecated since Symfony 4.3, use max[self]=0 instead - */ - const MODE_WEAK_VENDORS = 'weak_vendors'; - const MODE_DISABLED = 'disabled'; const MODE_WEAK = 'max[total]=999999&verbose=0'; const MODE_STRICT = 'max[total]=0'; private $mode; private $configuration; - private $deprecations = [ - 'unsilencedCount' => 0, - 'remaining selfCount' => 0, - 'legacyCount' => 0, - 'otherCount' => 0, - 'remaining directCount' => 0, - 'remaining indirectCount' => 0, - 'unsilenced' => [], - 'remaining self' => [], - 'legacy' => [], - 'other' => [], - 'remaining direct' => [], - 'remaining indirect' => [], - ]; + + /** + * @var DeprecationGroup[] + */ + private $deprecationGroups = []; private static $isRegistered = false; private static $isAtLeastPhpUnit83; + public function __construct() + { + $this->resetDeprecationGroups(); + } + /** * Registers and configures the deprecation handler. * @@ -149,9 +141,9 @@ public function handleError($type, $msg, $file, $line, $context = []) $group = 'legacy'; } else { $group = [ - Deprecation::TYPE_SELF => 'remaining self', - Deprecation::TYPE_DIRECT => 'remaining direct', - Deprecation::TYPE_INDIRECT => 'remaining indirect', + Deprecation::TYPE_SELF => 'self', + Deprecation::TYPE_DIRECT => 'direct', + Deprecation::TYPE_INDIRECT => 'indirect', Deprecation::TYPE_UNDETERMINED => 'other', ][$deprecation->getType()]; } @@ -162,18 +154,14 @@ public function handleError($type, $msg, $file, $line, $context = []) exit(1); } if ('legacy' !== $group) { - $ref = &$this->deprecations[$group][$msg]['count']; - ++$ref; - $ref = &$this->deprecations[$group][$msg][$class.'::'.$method]; - ++$ref; + $this->deprecationGroups[$group]->addNoticeFromObject($msg, $class, $method); + } else { + $this->deprecationGroups[$group]->addNotice(); } } else { - $ref = &$this->deprecations[$group][$msg]['count']; - ++$ref; + $this->deprecationGroups[$group]->addNoticeFromProceduralCode($msg); } - ++$this->deprecations[$group.'Count']; - return null; } @@ -198,34 +186,44 @@ public function shutdown() echo "\n", self::colorize('THE ERROR HANDLER HAS CHANGED!', true), "\n"; } - $groups = ['unsilenced', 'remaining self', 'remaining direct', 'remaining indirect', 'legacy', 'other']; - - $this->displayDeprecations($groups, $configuration); + $groups = array_keys($this->deprecationGroups); // store failing status - $isFailing = !$configuration->tolerates($this->deprecations); + $isFailing = !$configuration->tolerates($this->deprecationGroups); - // reset deprecations array - foreach ($this->deprecations as $group => $arrayOrInt) { - $this->deprecations[$group] = \is_int($arrayOrInt) ? 0 : []; - } + $this->displayDeprecations($groups, $configuration, $isFailing); + + $this->resetDeprecationGroups(); register_shutdown_function(function () use ($isFailing, $groups, $configuration) { - foreach ($this->deprecations as $group => $arrayOrInt) { - if (0 < (\is_int($arrayOrInt) ? $arrayOrInt : \count($arrayOrInt))) { + foreach ($this->deprecationGroups as $group) { + if ($group->count() > 0) { echo "Shutdown-time deprecations:\n"; break; } } - $this->displayDeprecations($groups, $configuration); + $isFailingAtShutdown = !$configuration->tolerates($this->deprecationGroups); + $this->displayDeprecations($groups, $configuration, $isFailingAtShutdown); - if ($isFailing || !$configuration->tolerates($this->deprecations)) { + if ($isFailing || $isFailingAtShutdown) { exit(1); } }); } + private function resetDeprecationGroups() + { + $this->deprecationGroups = [ + 'unsilenced' => new DeprecationGroup(), + 'self' => new DeprecationGroup(), + 'direct' => new DeprecationGroup(), + 'indirect' => new DeprecationGroup(), + 'legacy' => new DeprecationGroup(), + 'other' => new DeprecationGroup(), + ]; + } + private function getConfiguration() { if (null !== $this->configuration) { @@ -249,13 +247,6 @@ private function getConfiguration() if ('weak' === $mode) { return $this->configuration = Configuration::inWeakMode(); } - if (self::MODE_WEAK_VENDORS === $mode) { - ++$this->deprecations['remaining directCount']; - $msg = sprintf('Setting SYMFONY_DEPRECATIONS_HELPER to "%s" is deprecated in favor of "max[self]=0"', $mode); - $ref = &$this->deprecations['remaining direct'][$msg]['count']; - ++$ref; - $mode = 'max[self]=0'; - } if (isset($mode[0]) && '/' === $mode[0]) { return $this->configuration = Configuration::fromRegex($mode); } @@ -291,31 +282,38 @@ private static function colorize($str, $red) /** * @param string[] $groups * @param Configuration $configuration + * @param bool $isFailing */ - private function displayDeprecations($groups, $configuration) + private function displayDeprecations($groups, $configuration, $isFailing) { $cmp = function ($a, $b) { - return $b['count'] - $a['count']; + return $b->count() - $a->count(); }; foreach ($groups as $group) { - if ($this->deprecations[$group.'Count']) { + if ($this->deprecationGroups[$group]->count()) { echo "\n", self::colorize( - sprintf('%s deprecation notices (%d)', ucfirst($group), $this->deprecations[$group.'Count']), - 'legacy' !== $group && 'remaining indirect' !== $group + sprintf( + '%s deprecation notices (%d)', + \in_array($group, ['direct', 'indirect', 'self'], true) ? "Remaining $group" : ucfirst($group), + $this->deprecationGroups[$group]->count() + ), + 'legacy' !== $group && 'indirect' !== $group ), "\n"; - if (!$configuration->verboseOutput()) { + if ('legacy' !== $group && !$configuration->verboseOutput($group) && !$isFailing) { continue; } - uasort($this->deprecations[$group], $cmp); + $notices = $this->deprecationGroups[$group]->notices(); + uasort($notices, $cmp); - foreach ($this->deprecations[$group] as $msg => $notices) { - echo "\n ", $notices['count'], 'x: ', $msg, "\n"; + foreach ($notices as $msg => $notice) { + echo "\n ", $notice->count(), 'x: ', $msg, "\n"; - arsort($notices); + $countsByCaller = $notice->getCountsByCaller(); + arsort($countsByCaller); - foreach ($notices as $method => $count) { + foreach ($countsByCaller as $method => $count) { if ('count' !== $method) { echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php index 6b42814bbc906..75c7bf888cb8c 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php @@ -32,17 +32,17 @@ class Configuration private $enabled = true; /** - * @var bool + * @var bool[] */ - private $verboseOutput = true; + private $verboseOutput; /** * @param int[] $thresholds A hash associating groups to thresholds * @param string $regex Will be matched against messages, to decide * whether to display a stack trace - * @param bool $verboseOutput + * @param bool[] $verboseOutput Keyed by groups */ - private function __construct(array $thresholds = [], $regex = '', $verboseOutput = true) + private function __construct(array $thresholds = [], $regex = '', $verboseOutput = []) { $groups = ['total', 'indirect', 'direct', 'self']; @@ -72,7 +72,21 @@ private function __construct(array $thresholds = [], $regex = '', $verboseOutput } } $this->regex = $regex; - $this->verboseOutput = $verboseOutput; + + $this->verboseOutput = [ + 'unsilenced' => true, + 'direct' => true, + 'indirect' => true, + 'self' => true, + 'other' => true, + ]; + + foreach ($verboseOutput as $group => $status) { + if (!isset($this->verboseOutput[$group])) { + throw new \InvalidArgumentException(sprintf('Unsupported verbosity group "%s", expected one of "%s"', $group, implode('", "', array_keys($this->verboseOutput)))); + } + $this->verboseOutput[$group] = (bool) $status; + } } /** @@ -84,24 +98,26 @@ public function isEnabled() } /** - * @param mixed[] $deprecations + * @param DeprecationGroup[] $deprecationGroups * * @return bool */ - public function tolerates(array $deprecations) + public function tolerates(array $deprecationGroups) { - $deprecationCounts = []; - foreach ($deprecations as $key => $deprecation) { - if (false !== strpos($key, 'Count') && false === strpos($key, 'legacy')) { - $deprecationCounts[$key] = $deprecation; + $grandTotal = 0; + + foreach ($deprecationGroups as $name => $group) { + if ('legacy' !== $name) { + $grandTotal += $group->count(); } } - if (array_sum($deprecationCounts) > $this->thresholds['total']) { + if ($grandTotal > $this->thresholds['total']) { return false; } + foreach (['self', 'direct', 'indirect'] as $deprecationType) { - if ($deprecationCounts['remaining '.$deprecationType.'Count'] > $this->thresholds[$deprecationType]) { + if ($deprecationGroups[$deprecationType]->count() > $this->thresholds[$deprecationType]) { return false; } } @@ -130,9 +146,9 @@ public function isInRegexMode() /** * @return bool */ - public function verboseOutput() + public function verboseOutput($group) { - return $this->verboseOutput; + return $this->verboseOutput[$group]; } /** @@ -145,7 +161,7 @@ public static function fromUrlEncodedString($serializedConfiguration) { parse_str($serializedConfiguration, $normalizedConfiguration); foreach (array_keys($normalizedConfiguration) as $key) { - if (!\in_array($key, ['max', 'disabled', 'verbose'], true)) { + if (!\in_array($key, ['max', 'disabled', 'verbose', 'quiet'], true)) { throw new \InvalidArgumentException(sprintf('Unknown configuration option "%s"', $key)); } } @@ -154,9 +170,19 @@ public static function fromUrlEncodedString($serializedConfiguration) return self::inDisabledMode(); } - $verboseOutput = true; - if (isset($normalizedConfiguration['verbose'])) { - $verboseOutput = (bool) $normalizedConfiguration['verbose']; + $verboseOutput = []; + if (!isset($normalizedConfiguration['verbose'])) { + $normalizedConfiguration['verbose'] = true; + } + + foreach (['unsilenced', 'direct', 'indirect', 'self', 'other'] as $group) { + $verboseOutput[$group] = (bool) $normalizedConfiguration['verbose']; + } + + if (isset($normalizedConfiguration['quiet']) && \is_array($normalizedConfiguration['quiet'])) { + foreach ($normalizedConfiguration['quiet'] as $shushedGroup) { + $verboseOutput[$shushedGroup] = false; + } } return new self( @@ -190,7 +216,12 @@ public static function inStrictMode() */ public static function inWeakMode() { - return new self([], '', false); + $verboseOutput = []; + foreach (['unsilenced', 'direct', 'indirect', 'self', 'other'] as $group) { + $verboseOutput[$group] = false; + } + + return new self([], '', $verboseOutput); } /** diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php new file mode 100644 index 0000000000000..ea62be4164185 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationGroup.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler; + +/** + * @internal + */ +final class DeprecationGroup +{ + private $count = 0; + + /** + * @var DeprecationNotice[] keys are messages + */ + private $deprecationNotices = []; + + /** + * @param string $message + * @param string $class + * @param string $method + */ + public function addNoticeFromObject($message, $class, $method) + { + $this->deprecationNotice($message)->addObjectOccurence($class, $method); + $this->addNotice(); + } + + /** + * @param string $message + */ + public function addNoticeFromProceduralCode($message) + { + $this->deprecationNotice($message)->addProceduralOccurence(); + $this->addNotice(); + } + + public function addNotice() + { + ++$this->count; + } + + /** + * @param string $message + * + * @return DeprecationNotice + */ + private function deprecationNotice($message) + { + if (!isset($this->deprecationNotices[$message])) { + $this->deprecationNotices[$message] = new DeprecationNotice(); + } + + return $this->deprecationNotices[$message]; + } + + public function count() + { + return $this->count; + } + + public function notices() + { + return $this->deprecationNotices; + } +} diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationNotice.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationNotice.php new file mode 100644 index 0000000000000..d76073c8c43e2 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/DeprecationNotice.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler; + +/** + * @internal + */ +final class DeprecationNotice +{ + private $count = 0; + + /** + * @var int[] + */ + private $countsByCaller = []; + + public function addObjectOccurence($class, $method) + { + if (!isset($this->countsByCaller["$class::$method"])) { + $this->countsByCaller["$class::$method"] = 0; + } + ++$this->countsByCaller["$class::$method"]; + ++$this->count; + } + + public function addProceduralOccurence() + { + ++$this->count; + } + + public function getCountsByCaller() + { + return $this->countsByCaller; + } + + public function count() + { + return $this->count; + } +} diff --git a/src/Symfony/Bridge/PhpUnit/ExpectDeprecationTrait.php b/src/Symfony/Bridge/PhpUnit/ExpectDeprecationTrait.php new file mode 100644 index 0000000000000..0db391d12abab --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/ExpectDeprecationTrait.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit; + +use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait; + +trait ExpectDeprecationTrait +{ + /** + * @param string $message + * + * @return void + */ + protected function expectDeprecation($message) + { + if (!SymfonyTestsListenerTrait::$previousErrorHandler) { + SymfonyTestsListenerTrait::$previousErrorHandler = set_error_handler([SymfonyTestsListenerTrait::class, 'handleError']); + } + + SymfonyTestsListenerTrait::$expectedDeprecations[] = $message; + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 1e030825e6fde..7f0f390f58cc1 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -13,6 +13,7 @@ use Doctrine\Common\Annotations\AnnotationRegistry; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\RiskyTestError; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; use PHPUnit\Runner\BaseTestRunner; @@ -20,6 +21,7 @@ use PHPUnit\Util\Test; use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Bridge\PhpUnit\DnsMock; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use Symfony\Component\ErrorHandler\DebugClassLoader; @@ -32,16 +34,16 @@ */ class SymfonyTestsListenerTrait { + public static $expectedDeprecations = []; + public static $previousErrorHandler; + private static $gatheredDeprecations = []; private static $globallyEnabled = false; private $state = -1; private $skippedFile = false; private $wasSkipped = []; private $isSkipped = []; - private $expectedDeprecations = []; - private $gatheredDeprecations = []; - private $previousErrorHandler; - private $error; private $runsInSeparateProcess = false; + private $checkNumAssertions = false; /** * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive) @@ -220,15 +222,17 @@ public function startTest($test) if (isset($annotations['class']['expectedDeprecation'])) { $test->getTestResultObject()->addError($test, new AssertionFailedError('`@expectedDeprecation` annotations are not allowed at the class level.'), 0); } - if (isset($annotations['method']['expectedDeprecation'])) { - if (!\in_array('legacy', $groups, true)) { - $this->error = new AssertionFailedError('Only tests with the `@group legacy` annotation can have `@expectedDeprecation`.'); + if (isset($annotations['method']['expectedDeprecation']) || $this->checkNumAssertions = \in_array(ExpectDeprecationTrait::class, class_uses($test), true)) { + if (isset($annotations['method']['expectedDeprecation'])) { + self::$expectedDeprecations = $annotations['method']['expectedDeprecation']; + self::$previousErrorHandler = set_error_handler([self::class, 'handleError']); } - $test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false); + if ($this->checkNumAssertions) { + $this->checkNumAssertions = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything() && !$test->doesNotPerformAssertions(); + } - $this->expectedDeprecations = $annotations['method']['expectedDeprecation']; - $this->previousErrorHandler = set_error_handler([$this, 'handleError']); + $test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false); } } } @@ -242,9 +246,12 @@ public function endTest($test, $time) $className = \get_class($test); $groups = Test::getGroups($className, $test->getName(false)); - if ($errored = null !== $this->error) { - $test->getTestResultObject()->addError($test, $this->error, 0); - $this->error = null; + if ($this->checkNumAssertions) { + if (!self::$expectedDeprecations && !$test->getNumAssertions()) { + $test->getTestResultObject()->addFailure($test, new RiskyTestError('This test did not perform any assertions'), $time); + } + + $this->checkNumAssertions = false; } if ($this->runsInSeparateProcess) { @@ -263,24 +270,26 @@ public function endTest($test, $time) $this->runsInSeparateProcess = false; } - if ($this->expectedDeprecations) { + if (self::$expectedDeprecations) { if (!\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE], true)) { - $test->addToAssertionCount(\count($this->expectedDeprecations)); + $test->addToAssertionCount(\count(self::$expectedDeprecations)); } restore_error_handler(); - if (!$errored && !\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR], true)) { + if (!\in_array('legacy', $groups, true)) { + $test->getTestResultObject()->addError($test, new AssertionFailedError('Only tests with the `@group legacy` annotation can expect a deprecation.'), 0); + } elseif (!\in_array($test->getStatus(), [BaseTestRunner::STATUS_SKIPPED, BaseTestRunner::STATUS_INCOMPLETE, BaseTestRunner::STATUS_FAILURE, BaseTestRunner::STATUS_ERROR], true)) { try { $prefix = "@expectedDeprecation:\n"; - $test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", $this->expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", $this->gatheredDeprecations)."\n"); + $test->assertStringMatchesFormat($prefix.'%A '.implode("\n%A ", self::$expectedDeprecations)."\n%A", $prefix.' '.implode("\n ", self::$gatheredDeprecations)."\n"); } catch (AssertionFailedError $e) { $test->getTestResultObject()->addFailure($test, $e, $time); } } - $this->expectedDeprecations = $this->gatheredDeprecations = []; - $this->previousErrorHandler = null; + self::$expectedDeprecations = self::$gatheredDeprecations = []; + self::$previousErrorHandler = null; } if (!$this->runsInSeparateProcess && -2 < $this->state && ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) { if (\in_array('time-sensitive', $groups, true)) { @@ -292,10 +301,10 @@ public function endTest($test, $time) } } - public function handleError($type, $msg, $file, $line, $context = []) + public static function handleError($type, $msg, $file, $line, $context = []) { if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { - $h = $this->previousErrorHandler; + $h = self::$previousErrorHandler; return $h ? $h($type, $msg, $file, $line, $context) : false; } @@ -308,7 +317,7 @@ public function handleError($type, $msg, $file, $line, $context = []) if (error_reporting()) { $msg = 'Unsilenced deprecation: '.$msg; } - $this->gatheredDeprecations[] = $msg; + self::$gatheredDeprecations[] = $msg; return null; } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php index 39e792cd3a2cb..bb5b3a72d4932 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Configuration; +use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\DeprecationGroup; class ConfigurationTest extends TestCase { @@ -47,122 +48,122 @@ public function testItThrowsOnStringishThreshold() public function testItNoticesExceededTotalThreshold() { $configuration = Configuration::fromUrlEncodedString('max[total]=3'); - $this->assertTrue($configuration->tolerates([ - 'unsilencedCount' => 1, - 'remaining selfCount' => 0, - 'legacyCount' => 1, - 'otherCount' => 0, - 'remaining directCount' => 1, - 'remaining indirectCount' => 1, - ])); - $this->assertFalse($configuration->tolerates([ - 'unsilencedCount' => 1, - 'remaining selfCount' => 1, - 'legacyCount' => 1, - 'otherCount' => 0, - 'remaining directCount' => 1, - 'remaining indirectCount' => 1, - ])); + $this->assertTrue($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1, + 'self' => 0, + 'legacy' => 1, + 'other' => 0, + 'direct' => 1, + 'indirect' => 1, + ]))); + $this->assertFalse($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1, + 'self' => 1, + 'legacy' => 1, + 'other' => 0, + 'direct' => 1, + 'indirect' => 1, + ]))); } public function testItNoticesExceededSelfThreshold() { $configuration = Configuration::fromUrlEncodedString('max[self]=1'); - $this->assertTrue($configuration->tolerates([ - 'unsilencedCount' => 1234, - 'remaining selfCount' => 1, - 'legacyCount' => 23, - 'otherCount' => 13, - 'remaining directCount' => 124, - 'remaining indirectCount' => 3244, - ])); - $this->assertFalse($configuration->tolerates([ - 'unsilencedCount' => 1234, - 'remaining selfCount' => 2, - 'legacyCount' => 23, - 'otherCount' => 13, - 'remaining directCount' => 124, - 'remaining indirectCount' => 3244, - ])); + $this->assertTrue($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1234, + 'self' => 1, + 'legacy' => 23, + 'other' => 13, + 'direct' => 124, + 'indirect' => 3244, + ]))); + $this->assertFalse($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1234, + 'self' => 2, + 'legacy' => 23, + 'other' => 13, + 'direct' => 124, + 'indirect' => 3244, + ]))); } public function testItNoticesExceededDirectThreshold() { $configuration = Configuration::fromUrlEncodedString('max[direct]=1&max[self]=999999'); - $this->assertTrue($configuration->tolerates([ - 'unsilencedCount' => 1234, - 'remaining selfCount' => 123, - 'legacyCount' => 23, - 'otherCount' => 13, - 'remaining directCount' => 1, - 'remaining indirectCount' => 3244, - ])); - $this->assertFalse($configuration->tolerates([ - 'unsilencedCount' => 1234, - 'remaining selfCount' => 124, - 'legacyCount' => 23, - 'otherCount' => 13, - 'remaining directCount' => 2, - 'remaining indirectCount' => 3244, - ])); + $this->assertTrue($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1234, + 'self' => 123, + 'legacy' => 23, + 'other' => 13, + 'direct' => 1, + 'indirect' => 3244, + ]))); + $this->assertFalse($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1234, + 'self' => 124, + 'legacy' => 23, + 'other' => 13, + 'direct' => 2, + 'indirect' => 3244, + ]))); } public function testItNoticesExceededIndirectThreshold() { $configuration = Configuration::fromUrlEncodedString('max[indirect]=1&max[direct]=999999&max[self]=999999'); - $this->assertTrue($configuration->tolerates([ - 'unsilencedCount' => 1234, - 'remaining selfCount' => 123, - 'legacyCount' => 23, - 'otherCount' => 13, - 'remaining directCount' => 1234, - 'remaining indirectCount' => 1, - ])); - $this->assertFalse($configuration->tolerates([ - 'unsilencedCount' => 1234, - 'remaining selfCount' => 124, - 'legacyCount' => 23, - 'otherCount' => 13, - 'remaining directCount' => 2324, - 'remaining indirectCount' => 2, - ])); + $this->assertTrue($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1234, + 'self' => 123, + 'legacy' => 23, + 'other' => 13, + 'direct' => 1234, + 'indirect' => 1, + ]))); + $this->assertFalse($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 1234, + 'self' => 124, + 'legacy' => 23, + 'other' => 13, + 'direct' => 2324, + 'indirect' => 2, + ]))); } public function testIndirectThresholdIsUsedAsADefaultForDirectAndSelfThreshold() { $configuration = Configuration::fromUrlEncodedString('max[indirect]=1'); - $this->assertTrue($configuration->tolerates([ - 'unsilencedCount' => 0, - 'remaining selfCount' => 1, - 'legacyCount' => 0, - 'otherCount' => 0, - 'remaining directCount' => 0, - 'remaining indirectCount' => 0, - ])); - $this->assertFalse($configuration->tolerates([ - 'unsilencedCount' => 0, - 'remaining selfCount' => 2, - 'legacyCount' => 0, - 'otherCount' => 0, - 'remaining directCount' => 0, - 'remaining indirectCount' => 0, - ])); - $this->assertTrue($configuration->tolerates([ - 'unsilencedCount' => 0, - 'remaining selfCount' => 0, - 'legacyCount' => 0, - 'otherCount' => 0, - 'remaining directCount' => 1, - 'remaining indirectCount' => 0, - ])); - $this->assertFalse($configuration->tolerates([ - 'unsilencedCount' => 0, - 'remaining selfCount' => 0, - 'legacyCount' => 0, - 'otherCount' => 0, - 'remaining directCount' => 2, - 'remaining indirectCount' => 0, - ])); + $this->assertTrue($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 0, + 'self' => 1, + 'legacy' => 0, + 'other' => 0, + 'direct' => 0, + 'indirect' => 0, + ]))); + $this->assertFalse($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 0, + 'self' => 2, + 'legacy' => 0, + 'other' => 0, + 'direct' => 0, + 'indirect' => 0, + ]))); + $this->assertTrue($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 0, + 'self' => 0, + 'legacy' => 0, + 'other' => 0, + 'direct' => 1, + 'indirect' => 0, + ]))); + $this->assertFalse($configuration->tolerates($this->buildGroups([ + 'unsilenced' => 0, + 'self' => 0, + 'legacy' => 0, + 'other' => 0, + 'direct' => 2, + 'indirect' => 0, + ]))); } public function testItCanTellWhetherToDisplayAStackTrace() @@ -184,12 +185,51 @@ public function testItCanBeDisabled() public function testItCanBeShushed() { $configuration = Configuration::fromUrlEncodedString('verbose'); - $this->assertFalse($configuration->verboseOutput()); + $this->assertFalse($configuration->verboseOutput('unsilenced')); + $this->assertFalse($configuration->verboseOutput('direct')); + $this->assertFalse($configuration->verboseOutput('indirect')); + $this->assertFalse($configuration->verboseOutput('self')); + $this->assertFalse($configuration->verboseOutput('other')); + } + + public function testItCanBePartiallyShushed() + { + $configuration = Configuration::fromUrlEncodedString('quiet[]=unsilenced&quiet[]=indirect&quiet[]=other'); + $this->assertFalse($configuration->verboseOutput('unsilenced')); + $this->assertTrue($configuration->verboseOutput('direct')); + $this->assertFalse($configuration->verboseOutput('indirect')); + $this->assertTrue($configuration->verboseOutput('self')); + $this->assertFalse($configuration->verboseOutput('other')); + } + + public function testItThrowsOnUnknownVerbosityGroup() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('made-up'); + Configuration::fromUrlEncodedString('quiet[]=made-up'); } public function testOutputIsNotVerboseInWeakMode() { $configuration = Configuration::inWeakMode(); - $this->assertFalse($configuration->verboseOutput()); + $this->assertFalse($configuration->verboseOutput('unsilenced')); + $this->assertFalse($configuration->verboseOutput('direct')); + $this->assertFalse($configuration->verboseOutput('indirect')); + $this->assertFalse($configuration->verboseOutput('self')); + $this->assertFalse($configuration->verboseOutput('other')); + } + + private function buildGroups($counts) + { + $groups = []; + foreach ($counts as $name => $count) { + $groups[$name] = new DeprecationGroup(); + $i = 0; + while ($i++ < $count) { + $groups[$name]->addNotice(); + } + } + + return $groups; } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationGroupTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationGroupTest.php new file mode 100644 index 0000000000000..df746e5e38907 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationGroupTest.php @@ -0,0 +1,30 @@ +addNoticeFromObject( + 'Calling sfContext::getInstance() is deprecated', + 'MonsterController', + 'get5klocMethod' + ); + $group->addNoticeFromProceduralCode('Calling sfContext::getInstance() is deprecated'); + $this->assertCount(1, $group->notices()); + $this->assertSame(2, $group->count()); + } + + public function testItAllowsAddingANoticeWithoutClutteringTheMemory() + { + // this is useful for notices in the legacy group + $group = new DeprecationGroup(); + $group->addNotice(); + $this->assertSame(1, $group->count()); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationNoticeTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationNoticeTest.php new file mode 100644 index 0000000000000..7fa500aa077b0 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationNoticeTest.php @@ -0,0 +1,35 @@ +addObjectOccurence('MyAction', '__invoke'); + $notice->addObjectOccurence('MyAction', '__invoke'); + $notice->addObjectOccurence('MyOtherAction', '__invoke'); + + $countsByCaller = $notice->getCountsByCaller(); + + $this->assertCount(2, $countsByCaller); + $this->assertArrayHasKey('MyAction::__invoke', $countsByCaller); + $this->assertArrayHasKey('MyOtherAction::__invoke', $countsByCaller); + $this->assertSame(2, $countsByCaller['MyAction::__invoke']); + $this->assertSame(1, $countsByCaller['MyOtherAction::__invoke']); + } + + public function testItCountsBothTypesOfOccurences() + { + $notice = new DeprecationNotice(); + $notice->addObjectOccurence('MyAction', '__invoke'); + $this->assertSame(1, $notice->count()); + + $notice->addProceduralOccurence(); + $this->assertSame(2, $notice->count()); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet.phpt new file mode 100644 index 0000000000000..d45c6f9af2687 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test DeprecationErrorHandler quiet on everything but indirect deprecations +--FILE-- + +--EXPECTF-- +Unsilenced deprecation notices (3) + +Remaining direct deprecation notices (1) + +Remaining indirect deprecation notices (1) + + 1x: deprecatedApi is deprecated! You should stop relying on it! + 1x in SomeService::deprecatedApi from acme\lib + +Legacy deprecation notices (2) + +Other deprecation notices (1) + diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet_but_failing.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet_but_failing.phpt new file mode 100644 index 0000000000000..9c73d3c4430ae --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/quiet_but_failing.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test DeprecationErrorHandler when failing and not verbose +--FILE-- + +--EXPECTF-- +Remaining indirect deprecation notices (1) + + 1x: deprecatedApi is deprecated! You should stop relying on it! + 1x in SomeService::deprecatedApi from acme\lib diff --git a/src/Symfony/Bridge/PhpUnit/Tests/ExpectDeprecationTraitTest.php b/src/Symfony/Bridge/PhpUnit/Tests/ExpectDeprecationTraitTest.php new file mode 100644 index 0000000000000..2d3f0e7a8b79f --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/ExpectDeprecationTraitTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; + +final class ExpectDeprecationTraitTest extends TestCase +{ + use ExpectDeprecationTrait; + + /** + * Do not remove this test in the next major version. + * + * @group legacy + */ + public function testOne() + { + $this->expectDeprecation('foo'); + @trigger_error('foo', E_USER_DEPRECATED); + } + + /** + * Do not remove this test in the next major version. + * + * @group legacy + */ + public function testMany() + { + $this->expectDeprecation('foo'); + $this->expectDeprecation('bar'); + @trigger_error('foo', E_USER_DEPRECATED); + @trigger_error('bar', E_USER_DEPRECATED); + } + + /** + * Do not remove this test in the next major version. + * + * @group legacy + * + * @expectedDeprecation foo + */ + public function testOneWithAnnotation() + { + $this->expectDeprecation('bar'); + @trigger_error('foo', E_USER_DEPRECATED); + @trigger_error('bar', E_USER_DEPRECATED); + } + + /** + * Do not remove this test in the next major version. + * + * @group legacy + * + * @expectedDeprecation foo + * @expectedDeprecation bar + */ + public function testManyWithAnnotation() + { + $this->expectDeprecation('ccc'); + $this->expectDeprecation('fcy'); + @trigger_error('foo', E_USER_DEPRECATED); + @trigger_error('bar', E_USER_DEPRECATED); + @trigger_error('ccc', E_USER_DEPRECATED); + @trigger_error('fcy', E_USER_DEPRECATED); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index da2e101784961..e1a8737b37469 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -39,7 +39,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" }, "thanks": { "name": "phpunit/phpunit", diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 7b6ce56b0fbe9..eee54e5ca25c8 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -38,7 +38,7 @@ public function __construct() /** * {@inheritdoc} */ - public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator) + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator) { return $this->factory->createProxy( $this->factory->getGenerator()->getProxifiedClass($definition), diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index c949ef3a9786a..a9772a2b87342 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -48,7 +48,7 @@ public function isProxyCandidate(Definition $definition): bool /** * {@inheritdoc} */ - public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = null): string + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string { $instantiation = 'return'; @@ -56,10 +56,6 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = $instantiation .= sprintf(' $this->%s[%s] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', var_export($id, true)); } - if (null === $factoryCode) { - throw new \InvalidArgumentException(sprintf('Missing factory code to construct the service "%s".', $id)); - } - $proxyClass = $this->getProxyClassName($definition); return <<expectException('InvalidArgumentException'); - $this->expectExceptionMessage('Missing factory code to construct the service "foo".'); - $definition = new Definition(__CLASS__); - $definition->setLazy(true); - $this->dumper->getProxyFactoryCode($definition, 'foo'); - } - public function testGetProxyFactoryCodeForInterface() { $class = DummyClass::class; diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index c7038a9c19d48..99effe166c816 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -16,12 +16,12 @@ } ], "require": { - "php": "^7.1.3", - "symfony/dependency-injection": "^4.0|^5.0", + "php": "^7.2.5", + "symfony/dependency-injection": "^5.0", "ocramius/proxy-manager": "~2.1" }, "require-dev": { - "symfony/config": "^3.4|^4.0|^5.0" + "symfony/config": "^4.4|^5.0" }, "conflict": { "zendframework/zend-eventmanager": "2.6.0" @@ -35,7 +35,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } } } diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index 4a22265908b42..f5a6494ed29bc 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -39,14 +39,14 @@ public function setRequestStack(RequestStack $requestStack) $this->requestStack = $requestStack; } - public function setEnvironment($environment) + public function setEnvironment(string $environment) { $this->environment = $environment; } - public function setDebug($debug) + public function setDebug(bool $debug) { - $this->debug = (bool) $debug; + $this->debug = $debug; } /** diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 71a30ae880d78..70ca5e7481691 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,16 @@ CHANGELOG ========= +5.0.0 +----- + + * removed `TwigEngine` class, use `\Twig\Environment` instead. + * removed `transChoice` filter and token + * `HttpFoundationExtension` requires a `UrlHelper` on instantiation + * removed support for implicit STDIN usage in the `lint:twig` command, use `lint:twig -` (append a dash) instead to make it explicit. + * added form theme for Foundation 6 + * added support for Foundation 6 switches: add the `switch-input` class to the attributes of a `CheckboxType` + 4.4.0 ----- diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index edefbd8bcb452..2f365c216b423 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -38,15 +38,10 @@ class DebugCommand extends Command private $projectDir; private $bundlesMetadata; private $twigDefaultPath; - private $rootDir; private $filesystemLoaders; private $fileLinkFormatter; - /** - * @param FileLinkFormatter|null $fileLinkFormatter - * @param string|null $rootDir - */ - public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, $fileLinkFormatter = null, $rootDir = null) + public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null) { parent::__construct(); @@ -54,16 +49,7 @@ public function __construct(Environment $twig, string $projectDir = null, array $this->projectDir = $projectDir; $this->bundlesMetadata = $bundlesMetadata; $this->twigDefaultPath = $twigDefaultPath; - - if (\is_string($fileLinkFormatter) || $rootDir instanceof FileLinkFormatter) { - @trigger_error(sprintf('Passing a string as "$fileLinkFormatter" 5th argument or an instance of FileLinkFormatter as "$rootDir" 6th argument of the "%s()" method is deprecated since Symfony 4.4, swap the variables position.', __METHOD__), E_USER_DEPRECATED); - - $this->rootDir = $fileLinkFormatter; - $this->fileLinkFormatter = $rootDir; - } else { - $this->fileLinkFormatter = $fileLinkFormatter; - $this->rootDir = $rootDir; - } + $this->fileLinkFormatter = $fileLinkFormatter; } protected function configure() @@ -405,22 +391,6 @@ private function findWrongBundleOverrides(): array $alternatives = []; $bundleNames = []; - if ($this->rootDir && $this->projectDir) { - $folders = glob($this->rootDir.'/Resources/*/views', GLOB_ONLYDIR); - $relativePath = ltrim(substr($this->rootDir.\DIRECTORY_SEPARATOR.'Resources/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); - $bundleNames = array_reduce($folders, function ($carry, $absolutePath) use ($relativePath) { - if (0 === strpos($absolutePath, $this->projectDir)) { - $name = basename(\dirname($absolutePath)); - $path = ltrim($relativePath.$name, \DIRECTORY_SEPARATOR); - $carry[$name] = $path; - - @trigger_error(sprintf('Loading Twig templates from the "%s" directory is deprecated since Symfony 4.2, use "%s" instead.', $absolutePath, $this->twigDefaultPath.'/bundles/'.$name), E_USER_DEPRECATED); - } - - return $carry; - }, $bundleNames); - } - if ($this->twigDefaultPath && $this->projectDir) { $folders = glob($this->twigDefaultPath.'/bundles/*', GLOB_ONLYDIR); $relativePath = ltrim(substr($this->twigDefaultPath.'/bundles/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 20db8c5de12fd..a41e3ea9a5100 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -85,13 +85,6 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$filenames) { - // @deprecated to be removed in 5.0 - if (0 === ftell(STDIN)) { - @trigger_error('Piping content from STDIN to the "lint:twig" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', E_USER_DEPRECATED); - - return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]); - } - $loader = $this->twig->getLoader(); if ($loader instanceof FilesystemLoader) { $paths = []; @@ -144,7 +137,7 @@ private function getFilesInfo(array $filenames): array return $filesInfo; } - protected function findFiles($filename) + protected function findFiles(string $filename) { if (is_file($filename)) { return [$filename]; diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index fd6bc2d1e2663..be432838ff45a 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -26,7 +26,7 @@ * * @author Fabien Potencier * - * @final since Symfony 4.4 + * @final */ class TwigDataCollector extends DataCollector implements LateDataCollectorInterface { @@ -42,10 +42,8 @@ public function __construct(Profile $profile, Environment $twig = null) /** * {@inheritdoc} - * - * @param \Throwable|null $exception */ - public function collect(Request $request, Response $response/*, \Throwable $exception = null*/) + public function collect(Request $request, Response $response, \Throwable $exception = null) { } diff --git a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index 7224c5f34a219..d149c626a0027 100644 --- a/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -16,8 +16,6 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\RequestStack; use Twig\Environment; -use Twig\Error\LoaderError; -use Twig\Loader\ExistsLoaderInterface; /** * Provides the ability to render custom Twig-based HTML error pages @@ -58,7 +56,6 @@ public function render(\Throwable $exception): FlattenException } return $exception->setAsString($this->twig->render($template, [ - 'legacy' => false, // to be removed in 5.0 'exception' => $exception, 'status_code' => $exception->getStatusCode(), 'status_text' => $exception->getStatusText(), @@ -79,39 +76,15 @@ public static function isDebug(RequestStack $requestStack, bool $debug): \Closur private function findTemplate(int $statusCode): ?string { $template = sprintf('@Twig/Exception/error%s.html.twig', $statusCode); - if ($this->templateExists($template)) { + if ($this->twig->getLoader()->exists($template)) { return $template; } $template = '@Twig/Exception/error.html.twig'; - if ($this->templateExists($template)) { + if ($this->twig->getLoader()->exists($template)) { return $template; } return null; } - - /** - * To be removed in 5.0. - * - * Use instead: - * - * $this->twig->getLoader()->exists($template) - */ - private function templateExists(string $template): bool - { - $loader = $this->twig->getLoader(); - if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) { - return $loader->exists($template); - } - - try { - $loader->getSourceContext($template); - - return true; - } catch (LoaderError $e) { - } - - return false; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php index 62e7b91cb2db6..694821f7bf6b8 100644 --- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -19,10 +19,8 @@ * Twig extension for the Symfony Asset component. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class AssetExtension extends AbstractExtension +final class AssetExtension extends AbstractExtension { private $packages; @@ -33,10 +31,8 @@ public function __construct(Packages $packages) /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('asset', [$this, 'getAssetUrl']), @@ -49,37 +45,17 @@ public function getFunctions() * * If the package used to generate the path is an instance of * UrlPackage, you will always get a URL and not a path. - * - * @param string $path A public path - * @param string $packageName The name of the asset package to use - * - * @return string The public path of the asset */ - public function getAssetUrl($path, $packageName = null) + public function getAssetUrl(string $path, string $packageName = null): string { return $this->packages->getUrl($path, $packageName); } /** * Returns the version of an asset. - * - * @param string $path A public path - * @param string $packageName The name of the asset package to use - * - * @return string The asset version */ - public function getAssetVersion($path, $packageName = null) + public function getAssetVersion(string $path, string $packageName = null): string { return $this->packages->getVersion($path, $packageName); } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ - public function getName() - { - return 'asset'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index d91e1c1676000..f35725af0da9e 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -19,10 +19,8 @@ * Twig extension relate to PHP code and used by the profiler and the default exception templates. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class CodeExtension extends AbstractExtension +final class CodeExtension extends AbstractExtension { private $fileLinkFormat; private $charset; @@ -30,8 +28,6 @@ class CodeExtension extends AbstractExtension /** * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files - * @param string $projectDir The project directory - * @param string $charset The charset */ public function __construct($fileLinkFormat, string $projectDir, string $charset) { @@ -42,10 +38,8 @@ public function __construct($fileLinkFormat, string $projectDir, string $charset /** * {@inheritdoc} - * - * @return TwigFilter[] */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html']]), @@ -61,7 +55,7 @@ public function getFilters() ]; } - public function abbrClass($class) + public function abbrClass(string $class): string { $parts = explode('\\', $class); $short = array_pop($parts); @@ -69,7 +63,7 @@ public function abbrClass($class) return sprintf('%s', $class, $short); } - public function abbrMethod($method) + public function abbrMethod(string $method): string { if (false !== strpos($method, '::')) { list($class, $method) = explode('::', $method, 2); @@ -85,12 +79,8 @@ public function abbrMethod($method) /** * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string */ - public function formatArgs($args) + public function formatArgs(array $args): string { $result = []; foreach ($args as $key => $item) { @@ -118,26 +108,16 @@ public function formatArgs($args) /** * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string */ - public function formatArgsAsText($args) + public function formatArgsAsText(array $args): string { return strip_tags($this->formatArgs($args)); } /** * Returns an excerpt of a code file around the given line number. - * - * @param string $file A file path - * @param int $line The selected line number - * @param int $srcContext The number of displayed lines around or -1 for the whole file - * - * @return string An HTML string */ - public function fileExcerpt($file, $line, $srcContext = 3) + public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?string { if (is_file($file) && is_readable($file)) { // highlight_file could throw warnings @@ -168,14 +148,8 @@ public function fileExcerpt($file, $line, $srcContext = 3) /** * Formats a file path. - * - * @param string $file An absolute file path - * @param int $line The line number - * @param string $text Use this text for the link rather than the file path - * - * @return string */ - public function formatFile($file, $line, $text = null) + public function formatFile(string $file, int $line, string $text = null): string { $file = trim($file); @@ -201,12 +175,9 @@ public function formatFile($file, $line, $text = null) /** * Returns the link for a given file/line pair. * - * @param string $file An absolute file path - * @param int $line The line number - * * @return string|false A link or false */ - public function getFileLink($file, $line) + public function getFileLink(string $file, int $line) { if ($fmt = $this->fileLinkFormat) { return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); @@ -226,7 +197,7 @@ public function getFileRelative(string $file): ?string return null; } - public function formatFileFromText($text) + public function formatFileFromText(string $text): string { return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { return 'in '.$this->formatFile($match[2], $match[3]); @@ -254,15 +225,7 @@ public function formatLogMessage(string $message, array $context): string return htmlspecialchars($message, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); } - /** - * {@inheritdoc} - */ - public function getName() - { - return 'code'; - } - - protected static function fixCodeMarkup($line) + protected static function fixCodeMarkup(string $line): string { // ending tag from previous line $opening = strpos($line, ' * @author Titouan Galopin - * - * @final since Symfony 4.4 */ -class CsrfExtension extends AbstractExtension +final class CsrfExtension extends AbstractExtension { /** * {@inheritdoc} diff --git a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php index ea857c7ed583b..c3d5da6470c25 100644 --- a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php @@ -16,10 +16,8 @@ /** * @author Christian Flothmann * @author Titouan Galopin - * - * @final since Symfony 4.4 */ -class CsrfRuntime +final class CsrfRuntime { private $csrfTokenManager; diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php index 1ba9863a9756f..2187ed99bfb2a 100644 --- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -17,17 +17,14 @@ use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\Template; -use Twig\TokenParser\TokenParserInterface; use Twig\TwigFunction; /** * Provides integration of the dump() function with Twig. * * @author Nicolas Grekas - * - * @final since Symfony 4.4 */ -class DumpExtension extends AbstractExtension +final class DumpExtension extends AbstractExtension { private $cloner; private $dumper; @@ -39,9 +36,9 @@ public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null) } /** - * @return TwigFunction[] + * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]), @@ -49,19 +46,14 @@ public function getFunctions() } /** - * @return TokenParserInterface[] + * {@inheritdoc} */ - public function getTokenParsers() + public function getTokenParsers(): array { return [new DumpTokenParser()]; } - public function getName() - { - return 'dump'; - } - - public function dump(Environment $env, $context) + public function dump(Environment $env, array $context): ?string { if (!$env->isDebug()) { return null; diff --git a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php index af7be97c4f9bd..8d2a35c99f682 100644 --- a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php @@ -19,35 +19,21 @@ * ExpressionExtension gives a way to create Expressions from a template. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class ExpressionExtension extends AbstractExtension +final class ExpressionExtension extends AbstractExtension { /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('expression', [$this, 'createExpression']), ]; } - public function createExpression($expression) + public function createExpression(string $expression): Expression { return new Expression($expression); } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ - public function getName() - { - return 'expression'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 174a5cc3fe4bb..0f4076db53275 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -15,7 +15,6 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormView; use Twig\Extension\AbstractExtension; -use Twig\TokenParser\TokenParserInterface; use Twig\TwigFilter; use Twig\TwigFunction; use Twig\TwigTest; @@ -25,17 +24,13 @@ * * @author Fabien Potencier * @author Bernhard Schussek - * - * @final since Symfony 4.4 */ -class FormExtension extends AbstractExtension +final class FormExtension extends AbstractExtension { /** * {@inheritdoc} - * - * @return TokenParserInterface[] */ - public function getTokenParsers() + public function getTokenParsers(): array { return [ // {% form_theme form "SomeBundle::widgets.twig" %} @@ -45,10 +40,8 @@ public function getTokenParsers() /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('form_widget', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), @@ -67,10 +60,8 @@ public function getFunctions() /** * {@inheritdoc} - * - * @return TwigFilter[] */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('humanize', ['Symfony\Component\Form\FormRenderer', 'humanize']), @@ -80,24 +71,14 @@ public function getFilters() /** * {@inheritdoc} - * - * @return TwigTest[] */ - public function getTests() + public function getTests(): array { return [ new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'), new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'), ]; } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'form'; - } } /** @@ -107,8 +88,6 @@ public function getName() * * @param string|array $selectedValue The selected value to compare * - * @return bool Whether the choice is selected - * * @see ChoiceView::isSelected() */ function twig_is_selected_choice(ChoiceView $choice, $selectedValue): bool diff --git a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php index fe9010c21e8b9..a9ee05c4d0093 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php @@ -12,9 +12,7 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\UrlHelper; -use Symfony\Component\Routing\RequestContext; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -22,47 +20,20 @@ * Twig extension for the Symfony HttpFoundation component. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class HttpFoundationExtension extends AbstractExtension +final class HttpFoundationExtension extends AbstractExtension { private $urlHelper; - /** - * @param UrlHelper $urlHelper - */ - public function __construct($urlHelper) + public function __construct(UrlHelper $urlHelper) { - if ($urlHelper instanceof UrlHelper) { - $this->urlHelper = $urlHelper; - - return; - } - - if (!$urlHelper instanceof RequestStack) { - throw new \TypeError(sprintf('The first argument must be an instance of "%s" or an instance of "%s".', UrlHelper::class, RequestStack::class)); - } - - @trigger_error(sprintf('Passing a "%s" instance as the first argument to the "%s" constructor is deprecated since Symfony 4.3, pass a "%s" instance instead.', RequestStack::class, __CLASS__, UrlHelper::class), E_USER_DEPRECATED); - - $requestContext = null; - if (2 === \func_num_args()) { - $requestContext = func_get_arg(1); - if (null !== $requestContext && !$requestContext instanceof RequestContext) { - throw new \TypeError(sprintf('The second argument must be an instance of "%s".', RequestContext::class)); - } - } - - $this->urlHelper = new UrlHelper($urlHelper, $requestContext); + $this->urlHelper = $urlHelper; } /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('absolute_url', [$this, 'generateAbsoluteUrl']), @@ -75,13 +46,9 @@ public function getFunctions() * * This method returns the path unchanged if no request is available. * - * @param string $path The path - * - * @return string The absolute URL - * * @see Request::getUriForPath() */ - public function generateAbsoluteUrl($path) + public function generateAbsoluteUrl(string $path): string { return $this->urlHelper->getAbsoluteUrl($path); } @@ -91,24 +58,10 @@ public function generateAbsoluteUrl($path) * * This method returns the path unchanged if no request is available. * - * @param string $path The path - * - * @return string The relative path - * * @see Request::getRelativeUriForPath() */ - public function generateRelativePath($path) + public function generateRelativePath(string $path): string { return $this->urlHelper->getRelativePath($path); } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ - public function getName() - { - return 'request'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index 286bc420c66c5..f4b3e4b66953c 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -19,15 +19,13 @@ * Provides integration with the HttpKernel component. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class HttpKernelExtension extends AbstractExtension +final class HttpKernelExtension extends AbstractExtension { /** - * @return TwigFunction[] + * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]), @@ -36,16 +34,8 @@ public function getFunctions() ]; } - public static function controller($controller, $attributes = [], $query = []) + public static function controller(string $controller, array $attributes = [], array $query = []): ControllerReference { return new ControllerReference($controller, $attributes, $query); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'http_kernel'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php index e0dbb5b356901..6aab9566f9855 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php @@ -18,10 +18,8 @@ * Provides integration with the HttpKernel component. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class HttpKernelRuntime +final class HttpKernelRuntime { private $handler; @@ -33,14 +31,11 @@ public function __construct(FragmentHandler $handler) /** * Renders a fragment. * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * @param array $options An array of options - * - * @return string The fragment content + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * * @see FragmentHandler::render() */ - public function renderFragment($uri, $options = []) + public function renderFragment($uri, array $options = []): string { $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline'; unset($options['strategy']); @@ -51,15 +46,11 @@ public function renderFragment($uri, $options = []) /** * Renders a fragment. * - * @param string $strategy A strategy name - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * @param array $options An array of options - * - * @return string The fragment content + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * * @see FragmentHandler::render() */ - public function renderFragmentStrategy($strategy, $uri, $options = []) + public function renderFragmentStrategy(string $strategy, $uri, array $options = []): string { return $this->handler->render($uri, $strategy, $options); } diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php index a6648dc072db1..071b9ff247f1d 100644 --- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php @@ -19,10 +19,8 @@ * LogoutUrlHelper provides generator functions for the logout URL to Twig. * * @author Jeremy Mikola - * - * @final since Symfony 4.4 */ -class LogoutUrlExtension extends AbstractExtension +final class LogoutUrlExtension extends AbstractExtension { private $generator; @@ -33,10 +31,8 @@ public function __construct(LogoutUrlGenerator $generator) /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('logout_url', [$this, 'getLogoutUrl']), @@ -48,10 +44,8 @@ public function getFunctions() * Generates the relative logout URL for the firewall. * * @param string|null $key The firewall key or null to use the current firewall key - * - * @return string The relative logout URL */ - public function getLogoutPath($key = null) + public function getLogoutPath(string $key = null): string { return $this->generator->getLogoutPath($key); } @@ -60,19 +54,9 @@ public function getLogoutPath($key = null) * Generates the absolute logout URL for the firewall. * * @param string|null $key The firewall key or null to use the current firewall key - * - * @return string The absolute logout URL */ - public function getLogoutUrl($key = null) + public function getLogoutUrl(string $key = null): string { return $this->generator->getLogoutUrl($key); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'logout_url'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php index a46f2cdbb8936..fcc4396f1c9a1 100644 --- a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php @@ -17,10 +17,8 @@ /** * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class ProfilerExtension extends BaseProfilerExtension +final class ProfilerExtension extends BaseProfilerExtension { private $stopwatch; private $events; @@ -33,10 +31,7 @@ public function __construct(Profile $profile, Stopwatch $stopwatch = null) $this->events = new \SplObjectStorage(); } - /** - * @return void - */ - public function enter(Profile $profile) + public function enter(Profile $profile): void { if ($this->stopwatch && $profile->isTemplate()) { $this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template'); @@ -45,10 +40,7 @@ public function enter(Profile $profile) parent::enter($profile); } - /** - * @return void - */ - public function leave(Profile $profile) + public function leave(Profile $profile): void { parent::leave($profile); @@ -57,12 +49,4 @@ public function leave(Profile $profile) unset($this->events[$profile]); } } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'native_profiler'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 1ba528546d6d2..800c22f6d4c2c 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -22,10 +22,8 @@ * Provides integration of the Routing component with Twig. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class RoutingExtension extends AbstractExtension +final class RoutingExtension extends AbstractExtension { private $generator; @@ -36,10 +34,8 @@ public function __construct(UrlGeneratorInterface $generator) /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), @@ -47,26 +43,12 @@ public function getFunctions() ]; } - /** - * @param string $name - * @param array $parameters - * @param bool $relative - * - * @return string - */ - public function getPath($name, $parameters = [], $relative = false) + public function getPath(string $name, array $parameters = [], bool $relative = false): string { return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); } - /** - * @param string $name - * @param array $parameters - * @param bool $schemeRelative - * - * @return string - */ - public function getUrl($name, $parameters = [], $schemeRelative = false) + public function getUrl(string $name, array $parameters = [], bool $schemeRelative = false): string { return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); } @@ -92,8 +74,6 @@ public function getUrl($name, $parameters = [], $schemeRelative = false) * @param Node $argsNode The arguments of the path/url function * * @return array An array with the contexts the URL is safe - * - * @final */ public function isUrlGenerationSafe(Node $argsNode): array { @@ -110,12 +90,4 @@ public function isUrlGenerationSafe(Node $argsNode): array return []; } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'routing'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index 4acd7bbf9cc72..bd82ef20b4eff 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -21,10 +21,8 @@ * SecurityExtension exposes security context features. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class SecurityExtension extends AbstractExtension +final class SecurityExtension extends AbstractExtension { private $securityChecker; @@ -33,7 +31,10 @@ public function __construct(AuthorizationCheckerInterface $securityChecker = nul $this->securityChecker = $securityChecker; } - public function isGranted($role, $object = null, $field = null) + /** + * @param mixed $object + */ + public function isGranted($role, $object = null, string $field = null): bool { if (null === $this->securityChecker) { return false; @@ -52,21 +53,11 @@ public function isGranted($role, $object = null, $field = null) /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('is_granted', [$this, 'isGranted']), ]; } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'security'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php index f4b9a24ced5cd..80a25a949bdb5 100644 --- a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php @@ -20,10 +20,8 @@ * Twig extension for the stopwatch helper. * * @author Wouter J - * - * @final since Symfony 4.4 */ -class StopwatchExtension extends AbstractExtension +final class StopwatchExtension extends AbstractExtension { private $stopwatch; private $enabled; @@ -34,7 +32,7 @@ public function __construct(Stopwatch $stopwatch = null, bool $enabled = true) $this->enabled = $enabled; } - public function getStopwatch() + public function getStopwatch(): Stopwatch { return $this->stopwatch; } @@ -42,7 +40,7 @@ public function getStopwatch() /** * @return TokenParserInterface[] */ - public function getTokenParsers() + public function getTokenParsers(): array { return [ /* @@ -53,9 +51,4 @@ public function getTokenParsers() new StopwatchTokenParser(null !== $this->stopwatch && $this->enabled), ]; } - - public function getName() - { - return 'stopwatch'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index bd3c2eb8932d1..aa5a2dde3d746 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -13,45 +13,31 @@ use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; -use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser; use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser; use Symfony\Bridge\Twig\TokenParser\TransTokenParser; -use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorTrait; use Twig\Extension\AbstractExtension; use Twig\NodeVisitor\NodeVisitorInterface; -use Twig\TokenParser\AbstractTokenParser; use Twig\TwigFilter; /** * Provides integration of the Translation component with Twig. * * @author Fabien Potencier - * - * @final since Symfony 4.2 */ -class TranslationExtension extends AbstractExtension +final class TranslationExtension extends AbstractExtension { private $translator; private $translationNodeVisitor; - /** - * @param TranslatorInterface|null $translator - */ - public function __construct($translator = null, NodeVisitorInterface $translationNodeVisitor = null) + public function __construct(TranslatorInterface $translator = null, NodeVisitorInterface $translationNodeVisitor = null) { - if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) { - throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator))); - } $this->translator = $translator; $this->translationNodeVisitor = $translationNodeVisitor; } - /** - * @return TranslatorInterface|null - */ - public function getTranslator() + public function getTranslator(): TranslatorInterface { if (null === $this->translator) { if (!interface_exists(TranslatorInterface::class)) { @@ -68,33 +54,23 @@ public function getTranslator() /** * {@inheritdoc} - * - * @return TwigFilter[] */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('trans', [$this, 'trans']), - new TwigFilter('transchoice', [$this, 'transchoice'], ['deprecated' => '4.2', 'alternative' => 'trans" with parameter "%count%']), ]; } /** - * Returns the token parser instance to add to the existing list. - * - * @return AbstractTokenParser[] + * {@inheritdoc} */ - public function getTokenParsers() + public function getTokenParsers(): array { return [ // {% trans %}Symfony is great!{% endtrans %} new TransTokenParser(), - // {% transchoice count %} - // {0} There is no apples|{1} There is one apple|]1,Inf] There is {{ count }} apples - // {% endtranschoice %} - new TransChoiceTokenParser(), - // {% trans_default_domain "foobar" %} new TransDefaultDomainTokenParser(), ]; @@ -102,20 +78,18 @@ public function getTokenParsers() /** * {@inheritdoc} - * - * @return NodeVisitorInterface[] */ - public function getNodeVisitors() + public function getNodeVisitors(): array { return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()]; } - public function getTranslationNodeVisitor() + public function getTranslationNodeVisitor(): TranslationNodeVisitor { return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor(); } - public function trans($message, array $arguments = [], $domain = null, $locale = null, $count = null) + public function trans(string $message, array $arguments = [], string $domain = null, string $locale = null, int $count = null): string { if (null !== $count) { $arguments['%count%'] = $count; @@ -123,26 +97,4 @@ public function trans($message, array $arguments = [], $domain = null, $locale = return $this->getTranslator()->trans($message, $arguments, $domain, $locale); } - - /** - * @deprecated since Symfony 4.2, use the trans() method instead with a %count% parameter - */ - public function transchoice($message, $count, array $arguments = [], $domain = null, $locale = null) - { - $translator = $this->getTranslator(); - - if ($translator instanceof TranslatorInterface) { - return $translator->trans($message, array_merge(['%count%' => $count], $arguments), $domain, $locale); - } - - return $translator->transChoice($message, $count, array_merge(['%count%' => $count], $arguments), $domain, $locale); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'translator'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php index c2c6f8ba8fcf6..4b8217d932f4e 100644 --- a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php @@ -21,10 +21,8 @@ * Twig extension for the Symfony WebLink component. * * @author Kévin Dunglas - * - * @final since Symfony 4.4 */ -class WebLinkExtension extends AbstractExtension +final class WebLinkExtension extends AbstractExtension { private $requestStack; @@ -35,10 +33,8 @@ public function __construct(RequestStack $requestStack) /** * {@inheritdoc} - * - * @return TwigFunction[] */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('link', [$this, 'link']), @@ -53,13 +49,12 @@ public function getFunctions() /** * Adds a "Link" HTTP header. * - * @param string $uri The relation URI * @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch") * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The relation URI */ - public function link($uri, $rel, array $attributes = []) + public function link(string $uri, string $rel, array $attributes = []): string { if (!$request = $this->requestStack->getMasterRequest()) { return $uri; @@ -79,12 +74,11 @@ public function link($uri, $rel, array $attributes = []) /** * Preloads a resource. * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']") * * @return string The path of the asset */ - public function preload($uri, array $attributes = []) + public function preload(string $uri, array $attributes = []): string { return $this->link($uri, 'preload', $attributes); } @@ -92,12 +86,11 @@ public function preload($uri, array $attributes = []) /** * Resolves a resource origin as early as possible. * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function dnsPrefetch($uri, array $attributes = []) + public function dnsPrefetch(string $uri, array $attributes = []): string { return $this->link($uri, 'dns-prefetch', $attributes); } @@ -105,12 +98,11 @@ public function dnsPrefetch($uri, array $attributes = []) /** * Initiates a early connection to a resource (DNS resolution, TCP handshake, TLS negotiation). * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function preconnect($uri, array $attributes = []) + public function preconnect(string $uri, array $attributes = []): string { return $this->link($uri, 'preconnect', $attributes); } @@ -118,12 +110,11 @@ public function preconnect($uri, array $attributes = []) /** * Indicates to the client that it should prefetch this resource. * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function prefetch($uri, array $attributes = []) + public function prefetch(string $uri, array $attributes = []): string { return $this->link($uri, 'prefetch', $attributes); } @@ -131,12 +122,11 @@ public function prefetch($uri, array $attributes = []) /** * Indicates to the client that it should prerender this resource . * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function prerender($uri, array $attributes = []) + public function prerender(string $uri, array $attributes = []): string { return $this->link($uri, 'prerender', $attributes); } diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php index d5a2e759aa34a..d385567b13501 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -21,10 +21,8 @@ * WorkflowExtension. * * @author Grégoire Pineau - * - * @final since Symfony 4.4 */ -class WorkflowExtension extends AbstractExtension +final class WorkflowExtension extends AbstractExtension { private $workflowRegistry; @@ -34,9 +32,9 @@ public function __construct(Registry $workflowRegistry) } /** - * @return TwigFunction[] + * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('workflow_can', [$this, 'canTransition']), @@ -50,14 +48,8 @@ public function getFunctions() /** * Returns true if the transition is enabled. - * - * @param object $subject A subject - * @param string $transitionName A transition - * @param string $name A workflow name - * - * @return bool true if the transition is enabled */ - public function canTransition($subject, $transitionName, $name = null) + public function canTransition(object $subject, string $transitionName, string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->can($subject, $transitionName); } @@ -65,26 +57,17 @@ public function canTransition($subject, $transitionName, $name = null) /** * Returns all enabled transitions. * - * @param object $subject A subject - * @param string $name A workflow name - * * @return Transition[] All enabled transitions */ - public function getEnabledTransitions($subject, $name = null) + public function getEnabledTransitions(object $subject, string $name = null): array { return $this->workflowRegistry->get($subject, $name)->getEnabledTransitions($subject); } /** * Returns true if the place is marked. - * - * @param object $subject A subject - * @param string $placeName A place name - * @param string $name A workflow name - * - * @return bool true if the transition is enabled */ - public function hasMarkedPlace($subject, $placeName, $name = null) + public function hasMarkedPlace(object $subject, string $placeName, string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->getMarking($subject)->has($placeName); } @@ -92,13 +75,9 @@ public function hasMarkedPlace($subject, $placeName, $name = null) /** * Returns marked places. * - * @param object $subject A subject - * @param bool $placesNameOnly If true, returns only places name. If false returns the raw representation - * @param string $name A workflow name - * * @return string[]|int[] */ - public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null) + public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, string $name = null): array { $places = $this->workflowRegistry->get($subject, $name)->getMarking($subject)->getPlaces(); @@ -112,12 +91,11 @@ public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null) /** * Returns the metadata for a specific subject. * - * @param object $subject A subject * @param string|Transition|null $metadataSubject Use null to get workflow metadata * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata */ - public function getMetadata($subject, string $key, $metadataSubject = null, string $name = null): ?string + public function getMetadata(object $subject, string $key, $metadataSubject = null, string $name = null): ?string { return $this ->workflowRegistry @@ -127,15 +105,10 @@ public function getMetadata($subject, string $key, $metadataSubject = null, stri ; } - public function buildTransitionBlockerList($subject, string $transitionName, string $name = null): TransitionBlockerList + public function buildTransitionBlockerList(object $subject, string $transitionName, string $name = null): TransitionBlockerList { $workflow = $this->workflowRegistry->get($subject, $name); return $workflow->buildTransitionBlockerList($subject, $transitionName); } - - public function getName() - { - return 'workflow'; - } } diff --git a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php index 02d19d8ae3d3b..63df1336030bf 100644 --- a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php @@ -12,7 +12,6 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Yaml\Dumper as YamlDumper; -use Symfony\Component\Yaml\Yaml; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -20,17 +19,13 @@ * Provides integration of the Yaml component with Twig. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class YamlExtension extends AbstractExtension +final class YamlExtension extends AbstractExtension { /** * {@inheritdoc} - * - * @return TwigFilter[] */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('yaml_encode', [$this, 'encode']), @@ -38,7 +33,7 @@ public function getFilters() ]; } - public function encode($input, $inline = 0, $dumpObjects = 0) + public function encode($input, int $inline = 0, int $dumpObjects = 0): string { static $dumper; @@ -53,7 +48,7 @@ public function encode($input, $inline = 0, $dumpObjects = 0) return $dumper->dump($input, $inline, 0, false, $dumpObjects); } - public function dump($value, $inline = 0, $dumpObjects = false) + public function dump($value, int $inline = 0, int $dumpObjects = 0): string { if (\is_resource($value)) { return '%Resource%'; @@ -65,12 +60,4 @@ public function dump($value, $inline = 0, $dumpObjects = false) return $this->encode($value, $inline, $dumpObjects); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'yaml'; - } } diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php index 1e97ce3371d1d..bc3b82d2f595f 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php @@ -40,7 +40,7 @@ public function __construct(array $defaultThemes, Environment $environment) /** * {@inheritdoc} */ - public function renderBlock(FormView $view, $resource, $blockName, array $variables = []) + public function renderBlock(FormView $view, $resource, string $blockName, array $variables = []) { $cacheKey = $view->vars[self::CACHE_KEY_VAR]; @@ -70,13 +70,9 @@ public function renderBlock(FormView $view, $resource, $blockName, array $variab * * @see getResourceForBlock() * - * @param string $cacheKey The cache key of the form view - * @param FormView $view The form view for finding the applying themes - * @param string $blockName The name of the block to load - * * @return bool True if the resource could be loaded, false otherwise */ - protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName) + protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName) { // The caller guarantees that $this->resources[$cacheKey][$block] is // not set, but it doesn't have to check whether $this->resources[$cacheKey] @@ -143,14 +139,13 @@ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockNam /** * Loads the resources for all blocks in a theme. * - * @param string $cacheKey The cache key for storing the resource - * @param mixed $theme The theme to load the block from. This parameter - * is passed by reference, because it might be necessary - * to initialize the theme first. Any changes made to - * this variable will be kept and be available upon - * further calls to this method using the same theme. + * @param mixed $theme The theme to load the block from. This parameter + * is passed by reference, because it might be necessary + * to initialize the theme first. Any changes made to + * this variable will be kept and be available upon + * further calls to this method using the same theme. */ - protected function loadResourcesFromTheme($cacheKey, &$theme) + protected function loadResourcesFromTheme(string $cacheKey, &$theme) { if (!$theme instanceof Template) { /* @var Template $theme */ diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index d82d9ade1feaf..16718e8a75986 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -16,14 +16,12 @@ /** * @author Julien Galenski - * - * @final since Symfony 4.4 */ -class DumpNode extends Node +final class DumpNode extends Node { private $varPrefix; - public function __construct($varPrefix, Node $values = null, int $lineno, string $tag = null) + public function __construct($varPrefix, ?Node $values, int $lineno, string $tag = null) { $nodes = []; if (null !== $values) { @@ -34,10 +32,7 @@ public function __construct($varPrefix, Node $values = null, int $lineno, string $this->varPrefix = $varPrefix; } - /** - * @return void - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->write("if (\$this->env->isDebug()) {\n") diff --git a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php index b17243060f302..e37311267bb17 100644 --- a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php +++ b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php @@ -17,20 +17,15 @@ /** * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class FormThemeNode extends Node +final class FormThemeNode extends Node { public function __construct(Node $form, Node $resources, int $lineno, string $tag = null, bool $only = false) { parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag); } - /** - * @return void - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) diff --git a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php index 29402a8024fae..4d4cf61365772 100644 --- a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php +++ b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php @@ -21,15 +21,10 @@ * is "foo", the block "foo" will be rendered. * * @author Bernhard Schussek - * - * @final since Symfony 4.4 */ -class RenderBlockNode extends FunctionExpression +final class RenderBlockNode extends FunctionExpression { - /** - * @return void - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); $arguments = iterator_to_array($this->getNode('arguments')); diff --git a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php index 5db8d92244899..39cc1f7ffe939 100644 --- a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php +++ b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php @@ -18,15 +18,10 @@ /** * @author Bernhard Schussek - * - * @final since Symfony 4.4 */ -class SearchAndRenderBlockNode extends FunctionExpression +final class SearchAndRenderBlockNode extends FunctionExpression { - /** - * @return void - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); $compiler->raw('$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock('); diff --git a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php index b4dd8a9b37b4f..cfa4d8a197f9b 100644 --- a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php +++ b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php @@ -19,20 +19,15 @@ * Represents a stopwatch node. * * @author Wouter J - * - * @final since Symfony 4.4 */ -class StopwatchNode extends Node +final class StopwatchNode extends Node { public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, string $tag = null) { parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); } - /** - * @return void - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) diff --git a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php index 49ceac1404c5e..df29f0a19931f 100644 --- a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php @@ -17,20 +17,15 @@ /** * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class TransDefaultDomainNode extends Node +final class TransDefaultDomainNode extends Node { public function __construct(AbstractExpression $expr, int $lineno = 0, string $tag = null) { parent::__construct(['expr' => $expr], [], $lineno, $tag); } - /** - * @return void - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { // noop as this node is just a marker for TranslationDefaultDomainNodeVisitor } diff --git a/src/Symfony/Bridge/Twig/Node/TransNode.php b/src/Symfony/Bridge/Twig/Node/TransNode.php index 0a754fa859858..da2b85d1c2861 100644 --- a/src/Symfony/Bridge/Twig/Node/TransNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransNode.php @@ -19,15 +19,10 @@ use Twig\Node\Node; use Twig\Node\TextNode; -// BC/FC with namespaced Twig -class_exists('Twig\Node\Expression\ArrayExpression'); - /** * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class TransNode extends Node +final class TransNode extends Node { public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, int $lineno = 0, string $tag = null) { @@ -48,10 +43,7 @@ public function __construct(Node $body, Node $domain = null, AbstractExpression parent::__construct($nodes, [], $lineno, $tag); } - /** - * @return void - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); @@ -108,7 +100,7 @@ public function compile(Compiler $compiler) $compiler->raw(");\n"); } - protected function compileString(Node $body, ArrayExpression $vars, $ignoreStrictCheck = false) + private function compileString(Node $body, ArrayExpression $vars, bool $ignoreStrictCheck = false): array { if ($body instanceof ConstantExpression) { $msg = $body->getAttribute('value'); diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php index 642623f2a693c..765b4b69bd88c 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php @@ -50,14 +50,11 @@ public function leave() /** * Stores data into current scope. * - * @param string $key - * @param mixed $value - * * @return $this * * @throws \LogicException */ - public function set($key, $value) + public function set(string $key, $value) { if ($this->left) { throw new \LogicException('Left scope is not mutable.'); @@ -71,11 +68,9 @@ public function set($key, $value) /** * Tests if a data is visible from current scope. * - * @param string $key - * * @return bool */ - public function has($key) + public function has(string $key) { if (\array_key_exists($key, $this->data)) { return true; @@ -91,12 +86,9 @@ public function has($key) /** * Returns data visible from current scope. * - * @param string $key - * @param mixed $default - * * @return mixed */ - public function get($key, $default = null) + public function get(string $key, $default = null) { if (\array_key_exists($key, $this->data)) { return $this->data[$key]; diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 72badea2d2bd0..55bc3ae9a8959 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -27,10 +27,8 @@ /** * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor +final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor { private $scope; @@ -41,10 +39,8 @@ public function __construct() /** * {@inheritdoc} - * - * @return Node */ - protected function doEnterNode(Node $node, Environment $env) + protected function doEnterNode(Node $node, Environment $env): Node { if ($node instanceof BlockNode || $node instanceof ModuleNode) { $this->scope = $this->scope->enter(); @@ -95,10 +91,8 @@ protected function doEnterNode(Node $node, Environment $env) /** * {@inheritdoc} - * - * @return Node|null */ - protected function doLeaveNode(Node $node, Environment $env) + protected function doLeaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { return null; @@ -113,10 +107,8 @@ protected function doLeaveNode(Node $node, Environment $env) /** * {@inheritdoc} - * - * @return int */ - public function getPriority() + public function getPriority(): int { return -10; } diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index a72697deb37e2..89a15cd622c5d 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -22,45 +22,35 @@ * TranslationNodeVisitor extracts translation messages. * * @author Fabien Potencier - * - * @final since Symfony 4.4 */ -class TranslationNodeVisitor extends AbstractNodeVisitor +final class TranslationNodeVisitor extends AbstractNodeVisitor { const UNDEFINED_DOMAIN = '_undefined'; private $enabled = false; private $messages = []; - /** - * @return void - */ - public function enable() + public function enable(): void { $this->enabled = true; $this->messages = []; } - /** - * @return void - */ - public function disable() + public function disable(): void { $this->enabled = false; $this->messages = []; } - public function getMessages() + public function getMessages(): array { return $this->messages; } /** * {@inheritdoc} - * - * @return Node */ - protected function doEnterNode(Node $node, Environment $env) + protected function doEnterNode(Node $node, Environment $env): Node { if (!$this->enabled) { return $node; @@ -99,20 +89,16 @@ protected function doEnterNode(Node $node, Environment $env) /** * {@inheritdoc} - * - * @return Node|null */ - protected function doLeaveNode(Node $node, Environment $env) + protected function doLeaveNode(Node $node, Environment $env): ?Node { return $node; } /** * {@inheritdoc} - * - * @return int */ - public function getPriority() + public function getPriority(): int { return 0; } diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index 8ac32978a0925..462a75f863c00 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -123,7 +123,9 @@ {%- set type = type|default('file') -%} {{- block('form_widget_simple') -}} {%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim }) -%} -