diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 94f0fabcc4676..b6f39741d9dbc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | master for features / 2.7 up to 4.0 for bug fixes +| Branch? | master for features / 2.8 up to 4.1 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | BC breaks? | no diff --git a/.php_cs.dist b/.php_cs.dist index 3feb5f1f71a7a..2731440d1933b 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -28,6 +28,8 @@ return PhpCsFixer\Config::create() // fixture templates 'Symfony/Component/Templating/Tests/Fixtures/templates', 'Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom', + // generated fixtures + 'Symfony/Component/VarDumper/Tests/Fixtures', // resource templates 'Symfony/Bundle/FrameworkBundle/Resources/views/Form', )) diff --git a/CHANGELOG-2.8.md b/CHANGELOG-2.8.md new file mode 100644 index 0000000000000..522a83664793c --- /dev/null +++ b/CHANGELOG-2.8.md @@ -0,0 +1,1244 @@ +CHANGELOG for 2.8.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 2.8 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/v2.8.0...v2.8.1 + +* 2.8.41 (2018-05-25) + + * bug #27359 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization (nicolas-grekas) + * security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured + * security #cve-2018-11406 clear CSRF tokens when the user is logged out + * security #cve-2018-11385 Adding session authentication strategy to Guard to avoid session fixation + * security #cve-2018-11385 Adding session strategy to ALL listeners to avoid *any* possible fixation + * security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode + +* 2.8.40 (2018-05-21) + + * bug #26781 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions on transform() (syastrebov) + * bug #27286 [Translation] Add Occitan plural rule (kylekatarnls) + * bug #27246 Disallow invalid characters in session.name (ostrolucky) + * bug #24805 [Security] Fix logout (MatTheCat) + * bug #27141 [Process] Suppress warnings when open_basedir is non-empty (cbj4074) + * bug #27250 [Session] limiting :key for GET_LOCK to 64 chars (oleg-andreyev) + * bug #27237 [Debug] Fix populating error_get_last() for handled silent errors (nicolas-grekas) + * bug #27236 [Filesystem] Fix usages of error_get_last() (nicolas-grekas) + * bug #27152 [HttpFoundation] use brace-style regex delimiters (xabbuh) + * feature #24896 Add CODE_OF_CONDUCT.md (egircys) + +* 2.8.39 (2018-04-30) + + * bug #27067 [HttpFoundation] Fix setting session-related ini settings (e-moe) + * bug #27016 [Security][Guard] GuardAuthenticationProvider::authenticate cannot return null (biomedia-thomas) + * bug #26831 [Bridge/Doctrine] count(): Parameter must be an array or an object that implements Countable (gpenverne) + * bug #27044 [Security] Skip user checks if not implementing UserInterface (chalasr) + * bug #26014 [Security] Fixed being logged out on failed attempt in guard (iltar) + * bug #26910 Use new PHP7.2 functions in hasColorSupport (johnstevenson) + * bug #26999 [VarDumper] Fix dumping of SplObjectStorage (corphi) + * bug #25841 [DoctrineBridge] Fix bug when indexBy is meta key in PropertyInfo\DoctrineExtractor (insekticid) + * bug #26886 Don't assume that file binary exists on *nix OS (teohhanhui) + * bug #26643 Fix that ESI/SSI processing can turn a "private" response "public" (mpdude) + * bug #26932 [Form] Fixed trimming choice values (HeahDude) + * bug #26875 [Console] Don't go past exact matches when autocompleting (nicolas-grekas) + * bug #26823 [Validator] Fix LazyLoadingMetadataFactory with PSR6Cache for non classname if tested values isn't existing class (Pascal Montoya, pmontoya) + * bug #26834 [Yaml] Throw parse error on unfinished inline map (nicolas-grekas) + +* 2.8.38 (2018-04-06) + + * bug #26788 [Security] Load the user before pre/post auth checks when needed (chalasr) + * bug #26774 [SecurityBundle] Add missing argument to security.authentication.provider.simple (i3or1s, chalasr) + * bug #26763 [Finder] Remove duplicate slashes in filenames (helhum) + * bug #26749 Add PHPDbg support to HTTP components (hkdobrev) + * bug #26609 [Console] Fix check of color support on Windows (mlocati) + +* 2.8.37 (2018-04-02) + + * bug #26727 [HttpCache] Unlink tmp file on error (Chansig) + * bug #26675 [HttpKernel] DumpDataCollector: do not flush when a dumper is provided (ogizanagi) + * bug #26663 [TwigBridge] Fix rendering of currency by MoneyType (ro0NL) + * bug #26677 Support phpdbg SAPI in Debug::enable() (hkdobrev) + * bug #26589 [Ldap] cast to string when checking empty passwords (ismail1432) + * bug #26621 [Form] no type errors with invalid submitted data types (xabbuh) + * bug #26337 [Finder] Fixed leading/trailing / in filename (lyrixx) + * bug #26584 [TwigBridge] allow html5 compatible rendering of forms with null names (systemist) + * bug #24401 [Form] Change datetime to datetime-local for HTML5 datetime input (pierredup) + * bug #26370 [Security] added userChecker to SimpleAuthenticationProvider (i3or1s) + * bug #26569 [BrowserKit] Fix cookie path handling when $domain is null (dunglas) + * bug #26598 Fixes #26563 (open_basedir restriction in effect) (temperatur) + * bug #26568 [Debug] Reset previous exception handler earlier to prevent infinite loop (nicolas-grekas) + * bug #26567 [DoctrineBridge] Don't rely on ClassMetadataInfo->hasField in DoctrineOrmTypeGuesser anymore (fancyweb) + * bug #26356 [FrameworkBundle] HttpCache is not longer abstract (lyrixx) + * bug #26548 [DomCrawler] Change bad wording in ChoiceFormField::untick (dunglas) + * bug #26433 [DomCrawler] extract(): fix a bug when the attribute list is empty (dunglas) + * bug #26452 [Intl] Load locale aliases to support alias fallbacks (jakzal) + * bug #26450 [CssSelector] Fix CSS identifiers parsing - they can start with dash (jakubkulhan) + +* 2.8.36 (2018-03-05) + + * bug #26368 [WebProfilerBundle] Fix Debug toolbar breaks app (xkobal) + +* 2.8.35 (2018-03-01) + + * bug #26338 [Debug] Keep previous errors of Error instances (Philipp91) + * bug #26312 [Routing] Don't throw 405 when scheme requirement doesn't match (nicolas-grekas) + * bug #26298 Fix ArrayInput::toString() for InputArgument::IS_ARRAY args (maximium) + * bug #26236 [PropertyInfo] ReflectionExtractor: give a chance to other extractors if no properties (dunglas) + * bug #25557 [WebProfilerBundle] add a way to limit ajax request (Simperfit) + * bug #26228 [HttpFoundation] Fix missing "throw" in JsonResponse (nicolas-grekas) + * bug #26211 [Console] Suppress warning from sapi_windows_vt100_support (adawolfa) + * bug #26156 Fixes #26136: Avoid emitting warning in hasParameterOption() (greg-1-anderson) + * bug #26183 [DI] Add null check for removeChild (changmin.keum) + * bug #26173 [Security] fix accessing request values (xabbuh) + * bug #26159 created validator.tl.xlf for Form/Translations (ergiegonzaga) + * bug #26100 [Routing] Throw 405 instead of 404 when redirect is not possible (nicolas-grekas) + * bug #26040 [Process] Check PHP_BINDIR before $PATH in PhpExecutableFinder (nicolas-grekas) + * bug #26012 Exit as late as possible (greg0ire) + * bug #26111 [Security] fix merge of 2.7 into 2.8 + add test case (dmaicher) + * bug #25893 [Console] Fix hasParameterOption / getParameterOption when used with multiple flags (greg-1-anderson) + * bug #25940 [Form] keep the context when validating forms (xabbuh) + * bug #25373 Use the PCRE_DOLLAR_ENDONLY modifier in route regexes (mpdude) + * bug #26010 [CssSelector] For AND operator, the left operand should have parentheses, not only right operand (Arnaud CHASSEUX) + * bug #25971 [Debug] Fix bad registration of exception handler, leading to mem leak (nicolas-grekas) + * bug #25962 [Routing] Fix trailing slash redirection for non-safe verbs (nicolas-grekas) + * bug #25948 [Form] Fixed empty data on expanded ChoiceType and FileType (HeahDude) + * bug #25972 support sapi_windows_vt100_support for php 7.2+ (jhdxr) + * bug #25744 [TwigBridge] Allow label translation to be safe (MatTheCat) + +* 2.8.34 (2018-01-29) + + * bug #25922 [HttpFoundation] Use the correct syntax for session gc based on Pdo driver (tanasecosminromeo) + * bug #25933 Disable CSP header on exception pages only in debug (ostrolucky) + * bug #25926 [Form] Fixed Button::setParent() when already submitted (HeahDude) + * bug #25927 [Form] Fixed submitting disabled buttons (HeahDude) + * bug #25891 [DependencyInjection] allow null values for root nodes in YAML configs (xabbuh) + * bug #25848 [Validator] add missing parent isset and add test (Simperfit) + * bug #25861 do not conflict with egulias/email-validator 2.0+ (xabbuh) + * bug #25851 [Validator] Conflict with egulias/email-validator 2.0 (emodric) + * bug #25837 [SecurityBundle] Don't register in memory users as services (chalasr) + * bug #25835 [HttpKernel] DebugHandlersListener should always replace the existing exception handler (nicolas-grekas) + * bug #25829 [Debug] Always decorate existing exception handlers to deal with fatal errors (nicolas-grekas) + * bug #25824 Fixing a bug where the dump() function depended on bundle ordering (weaverryan) + * bug #25789 Enableable ArrayNodeDefinition is disabled for empty configuration (kejwmen) + * bug #25816 Problem in phar see mergerequest #25579 (betzholz) + * bug #25781 [Form] Disallow transform dates beyond the year 9999 (curry684) + * bug #25812 Copied NO language files to the new NB locale (derrabus) + * bug #25801 [Router] Skip anonymous classes when loading annotated routes (pierredup) + * bug #25657 [Security] Fix fatal error on non string username (chalasr) + * bug #25799 Fixed Request::__toString ignoring cookies (Toflar) + * bug #25755 [Debug] prevent infinite loop with faulty exception handlers (nicolas-grekas) + * bug #25771 [Validator] 19 digits VISA card numbers are valid (xabbuh) + * bug #25751 [FrameworkBundle] Add the missing `enabled` session attribute (sroze) + * bug #25750 [HttpKernel] Turn bad hosts into 400 instead of 500 (nicolas-grekas) + * bug #25490 [Serializer] Fixed throwing exception with option JSON_PARTIAL_OUTPUT_ON_ERROR (diversantvlz) + * bug #25709 Tweaked some styles in the profiler tables (javiereguiluz) + * feature #25669 [Security] Fail gracefully if the security token cannot be unserialized from the session (thewilkybarkid) + +* 2.8.33 (2018-01-05) + + * bug #25532 [HttpKernel] Disable CSP header on exception pages (ostrolucky) + * bug #25491 [Routing] Use the default host even if context is empty (sroze) + * bug #25662 Dumper shouldn't use html format for phpdbg / cli-server (jhoff) + * bug #25529 [Validator] Fix access to root object when using composite constraint (ostrolucky) + * bug #25430 Fixes for Oracle in PdoSessionHandler (elislenio) + * bug #25599 Add application/ld+json format associated to json (vincentchalamon) + * bug #25407 [Console] Commands with an alias should not be recognized as ambiguous (Simperfit) + * bug #25521 [Console] fix a bug when you are passing a default value and passing -n would output the index (Simperfit) + * bug #25489 [FrameworkBundle] remove esi/ssi renderers if inactive (dmaicher) + * bug #25427 Preserve percent-encoding in URLs when performing redirects in the UrlMatcher (mpdude) + * bug #25480 [FrameworkBundle] add missing validation options to XSD file (xabbuh) + * bug #25487 [Console] Fix a bug when passing a letter that could be an alias (Simperfit) + * bug #25233 [TwigBridge][Form] Fix hidden currency element with Bootstrap 3 theme (julienfalque) + * bug #25408 [Debug] Fix catching fatal errors in case of nested error handlers (nicolas-grekas) + * bug #25330 [HttpFoundation] Support 0 bit netmask in IPv6 (`::/0`) (stephank) + * bug #25410 [HttpKernel] Fix logging of post-terminate errors/exceptions (nicolas-grekas) + * bug #25323 [ExpressionLanguage] throw an SyntaxError instead of an undefined index notice (Simperfit) + +* 2.8.32 (2017-12-04) + + * bug #25278 Fix for missing whitespace control modifier in form layout (kubawerlos) + * bug #25236 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25258 [link] Prevent warnings when running link with 2.7 (dunglas) + * bug #24750 [Validator] ExpressionValidator should use OBJECT_TO_STRING (Simperfit) + * bug #25182 [HttpFoundation] AutExpireFlashBag should not clear new flashes (Simperfit, sroze) + * bug #25152 [Form] Don't rely on `Symfony\Component\HttpFoundation\File\File` if http-foundation isn't in FileType (issei-m) + * bug #24987 [Console] Fix global console flag when used in chain (Simperfit) + * bug #25043 [Yaml] added ability for substitute aliases when mapping is on single line (Michał Strzelecki, xabbuh) + * bug #25102 [Form] Fixed ContextErrorException in FileType (chihiro-adachi) + * bug #25130 [DI] Fix handling of inlined definitions by ContainerBuilder (nicolas-grekas) + * bug #25072 [Bridge/PhpUnit] Remove trailing "\n" from ClockMock::microtime(false) (joky) + * bug #24956 Fix ambiguous pattern (weltling) + +* 2.8.31 (2017-11-16) + + * security #24995 Validate redirect targets using the session cookie domain (nicolas-grekas) + * security #24994 Prevent bundle readers from breaking out of paths (xabbuh) + * security #24993 Ensure that submitted data are uploaded files (xabbuh) + * security #24992 Namespace generated CSRF tokens depending of the current scheme (dunglas) + +* 2.8.30 (2017-11-13) + + * bug #24952 [HttpFoundation] Fix session-related BC break (nicolas-grekas, sroze) + * bug #24929 [Console] Fix traversable autocomplete values (ro0NL) + +* 2.8.29 (2017-11-10) + + * bug #24888 [FrameworkBundle] Specifically inject the debug dispatcher in the collector (ogizanagi) + * bug #24909 [Intl] Update ICU data to 60.1 (jakzal) + * bug #24906 [Bridge/ProxyManager] Remove direct reference to value holder property (nicolas-grekas) + * bug #24900 [Validator] Fix Costa Rica IBAN format (Bozhidar Hristov) + * bug #24904 [Validator] Add Belarus IBAN format (Bozhidar Hristov) + * bug #24531 [HttpFoundation] Fix forward-compat of NativeSessionStorage with PHP 7.2 (sroze) + * bug #24665 Fix dump panel hidden when closing a dump (julienfalque) + * bug #24814 [Intl] Make intl-data tests pass and save language aliases again (jakzal) + * bug #24764 [HttpFoundation] add Early Hints to Reponse to fix test (Simperfit) + * bug #24605 [FrameworkBundle] Do not load property_access.xml if the component isn't installed (ogizanagi) + * bug #24606 [HttpFoundation] Fix FileBag issue with associative arrays (enumag) + * bug #24660 Escape trailing \ in QuestionHelper autocompletion (kamazee) + * bug #24644 [Security] Fixed auth provider authenticate() cannot return void (glye) + * bug #24642 [Routing] Fix resource miss (dunglas) + * bug #24608 Adding the Form default theme files to be warmed up in Twig's cache (weaverryan) + * bug #24626 streamed response should return $this (DQNEO) + * bug #24589 Username and password in basic auth are allowed to contain '.' (Richard Quadling) + * bug #24566 Fixed unsetting from loosely equal keys OrderedHashMap (maryo) + * bug #24570 [Debug] Fix same vendor detection in class loader (Jean-Beru) + * bug #24563 [Serializer] ObjectNormalizer: throw if PropertyAccess isn't installed (dunglas) + * bug #24571 [PropertyInfo] Add support for the iterable type (dunglas) + * bug #24579 pdo session fix (mxp100) + * bug #24536 [Security] Reject remember-me token if UserCheckerInterface::checkPostAuth() fails (kbond) + * bug #24519 [Validator] [Twig] added magic method __isset() to File Constraint class (loru88) + * bug #24532 [DI] Fix possible incorrect php-code when dumped strings contains newlines (Strate) + * bug #24502 [HttpFoundation] never match invalid IP addresses (xabbuh) + * bug #24460 [Form] fix parsing invalid floating point numbers (xabbuh) + * bug #24490 [HttpFoundation] Combine Cache-Control headers (c960657) + * bug #23711 Fix support for PHP 7.2 (Simperfit, nicolas-grekas) + * bug #24494 [HttpFoundation] Add missing session.lazy_write config option (nicolas-grekas) + * bug #24434 [Form] Use for=ID on radio/checkbox label. (Nyholm) + * bug #24455 [Console] Escape command usage (sroze) + +* 2.8.28 (2017-10-05) + + * bug #24448 [Session] fix MongoDb session handler to gc all expired sessions (Tobion) + * bug #24417 [Yaml] parse references on merge keys (xabbuh) + * bug #24421 [Config] Fix dumped files invalidation by OPCache (nicolas-grekas) + * bug #23980 Tests and fix for issue in array model data in EntityType field with multiple=true (stoccc) + * bug #22586 [Form] Fixed PercentToLocalizedStringTransformer to accept both comma and dot as decimal separator, if possible (aaa2000) + * bug #24157 [Intl] Fixed support of Locale::getFallback (lyrixx) + * bug #24198 [HttpFoundation] Fix file upload multiple with no files (enumag) + * bug #24036 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions and multiplications (Rubinum) + * bug #24367 PdoSessionHandler: fix advisory lock for pgsql (Tobion) + * bug #24243 HttpCache does not consider ESI resources in HEAD requests (mpdude) + * bug #24304 [FrameworkBundle] Fix Routing\DelegatingLoader (nicolas-grekas) + * bug #24219 [Console] Preserving line breaks between sentences according to the exception message (yceruto) + * bug #23722 [Form] Fixed GroupSequence with "constraints" option (HeahDude) + * bug #22321 [Filesystem] Fixed makePathRelative (ausi) + * bug #23473 [Filesystem] mirror - fix copying content with same name as source/target. (gitlost) + * bug #24162 [WebProfilerBundle] fixed TemplateManager when using Twig 2 without compat interfaces (fabpot) + * bug #24141 [DomCrawler] Fix conversion to int on GetPhpFiles (MaraBlaga) + * bug #23853 Filtering empty uuids in ORMQueryBuilderLoader. (mlazovla) + * bug #24101 [Security] Fix exception when use_referer option is true and referer is not set or empty (linniksa) + * bug #24105 [Filesystem] check permissions if dump target dir is missing (xabbuh) + * bug #24115 [FrameworkBundle] Get KERNEL_DIR through $_ENV too for KernelTestCase (yceruto) + * bug #24041 [ExpressionLanguage] throws an exception on calling uncallable method (fmata) + * bug #24096 Fix ArrayInput::toString() for VALUE_IS_ARRAY options/args (chalasr) + * bug #23730 Fixed the escaping of back slashes and << in console output (javiereguiluz) + +* 2.8.27 (2017-08-28) + + * bug #23989 [Debug] Remove false-positive check in DebugClassLoader (nicolas-grekas) + * bug #23982 [VarDumper] Strengthen dumped JS (nicolas-grekas) + * bug #23925 [Validator] Fix use of GroupSequenceProvider in child classes (linniksa) + * bug #23945 [Validator] Fix Greek translation (azhurb) + * bug #23909 [Console] Initialize lazily to render exceptions properly (nicolas-grekas) + * bug #23856 [DI] Fix dumping abstract with YamlDumper (nicolas-grekas) + * bug #23752 Ignore memcached missing key error on session destroy (jderusse) + * bug #23658 [HttpFoundation] Generate safe fallback filename for wrongly encoded filename (xelaris) + * bug #23783 Avoid infinite loops when profiler data is malformed (javiereguiluz) + * bug #23729 [Bridge\ProxyManager] Dont call __destruct() on non-instantiated services (nicolas-grekas) + +* 2.8.26 (2017-08-01) + + * bug #22244 [Console] Fix passing options with defaultCommand (Jakub Sacha) + * bug #23684 [Debug] Missing escape in debug output (c960657) + * bug #23662 [VarDumper] Adapt to php 7.2 changes (nicolas-grekas) + * bug #23649 [Form][TwigBridge] Don't render _method in form_rest() for a child form (fmarchalemisys) + * bug #23023 [DoctrineBridge][PropertyInfo] Added support for Doctrine Embeddables (vudaltsov) + * bug #23619 [Validator] Fix IbanValidator for ukrainian IBANs (paroe) + * bug #23238 [Security] ensure the 'route' index is set before attempting to use it (gsdevme) + * bug #23330 [WebProfilerBundle] Fix full sized dump hovering in toolbar (ogizanagi) + * bug #23580 Fix login redirect when referer contains a query string (fabpot) + * bug #23574 [VarDumper] Move locale sniffing to dump() time (nicolas-grekas) + +* 2.8.25 (2017-07-17) + + * security #23507 [Security] validate empty passwords again (xabbuh) + * bug #23526 [HttpFoundation] Set meta refresh time to 0 in RedirectResponse content (jnvsor) + * bug #23540 Disable inlining deprecated services (alekitto) + * bug #23468 [DI] Handle root namespace in service definitions (ro0NL) + * bug #23256 [Security] Fix authentication.failure event not dispatched on AccountStatusException (chalasr) + * bug #23461 Use rawurlencode() to transform the Cookie into a string (javiereguiluz) + * bug #23459 [TwigBundle] allow to configure custom formats in XML configs (xabbuh) + * bug #23460 Don't display the Symfony debug toolbar when printing the page (javiereguiluz) + * bug #23261 Fixed absolute url generation for query strings and hash urls (alexander-schranz) + * bug #23398 [Filesystem] Dont copy perms when origin is remote (nicolas-grekas) + +* 2.8.24 (2017-07-05) + + * bug #23378 [FrameworkBundle] Do not remove files from assets dir (1ed) + +* 2.8.23 (2017-07-04) + + * bug #23341 [DoctrineBridge][Security][Validator] do not validate empty values (xabbuh) + * bug #23274 Display a better error design when the toolbar cannot be displayed (yceruto) + * bug #23333 [PropertyAccess] Fix TypeError discard (dunglas) + * bug #23345 [Console] fix description of INF default values (xabbuh) + * bug #23279 Don't call count on non countable object (pierredup) + * bug #23283 [TwigBundle] add back exception check (xabbuh) + * bug #23268 Show exception is checked twice in ExceptionController of twig (gmponos) + * bug #23266 Display a better error message when the toolbar cannot be displayed (javiereguiluz) + * bug #23271 [FrameworkBundle] allow SSI fragments configuration in XML files (xabbuh) + * bug #23254 [Form][TwigBridge] render hidden _method field in form_rest() (xabbuh) + * bug #23250 [Translation] return fallback locales whenever possible (xabbuh) + * bug #23240 [Console] Fix catching exception type in QuestionHelper (voronkovich) + * bug #23229 [WebProfilerBundle] Eliminate line wrap on count column (routing) (e-moe) + * bug #22732 [Security] fix switch user _exit without having current token (dmaicher) + * bug #22730 [FrameworkBundle] Sessions: configurable "use_strict_mode" option for NativeSessionStorage (MacDada) + * bug #23195 [FrameworkBundle] [Command] Clean bundle directory, fixes #23177 (NicolasPion) + * bug #23052 [TwigBundle] Add Content-Type header for exception response (rchoquet) + * bug #23199 Reset redirectCount when throwing exception (hvanoch) + * bug #23186 [TwigBundle] Move template.xml loading to a compiler pass (ogizanagi) + * bug #23130 Keep s-maxage when expiry and validation are used in combination (mpdude) + * bug #23129 Fix two edge cases in ResponseCacheStrategy (mpdude) + * feature #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #23057 [Translation][FrameworkBundle] Fix resource loading order inconsistency reported in #23034 (mpdude) + * bug #23092 [Filesystem] added workaround in Filesystem::rename for PHP bug (VolCh) + * bug #23128 [HttpFoundation] fix for Support for new 7.1 session options (vincentaubert) + * bug #23176 [VarDumper] fixes (nicolas-grekas) + * bug #22953 #22839 - changed debug toolbar dump section to relative and use full window width (mkurzeja) + * bug #23086 [FrameworkBundle] Fix perf issue in CacheClearCommand::warmup() (nicolas-grekas) + * bug #23098 Cache ipCheck (2.7) (gonzalovilaseca) + * bug #23069 [SecurityBundle] Show unique Inherited roles in profile panel (yceruto) + +* 2.8.22 (2017-06-07) + + * bug #23073 [TwigBridge] Fix namespaced classes (ogizanagi) + * bug #22936 [Form] Mix attr option between guessed options and user options (yceruto) + * bug #22988 [PropertyInfo][DoctrineBridge] The bigint Doctrine's type must be converted to string (dunglas) + * bug #23014 Fix optional cache warmers are always instantiated whereas they should be lazy-loaded (romainneutron) + * bug #23024 [EventDispatcher] Fix ContainerAwareEventDispatcher::hasListeners(null) (nicolas-grekas) + * bug #22996 [Form] Fix \IntlDateFormatter timezone parameter usage to bypass PHP bug #66323 (romainneutron) + * bug #22994 Harden the debugging of Twig filters and functions (stof) + +* 2.8.21 (2017-05-29) + + * bug #22847 [Console] ChoiceQuestion must have choices (ro0NL) + * bug #22900 [FrameworkBundle][Console] Fix the override of a command registered by the kernel (aaa2000) + * bug #22910 [Filesystem] improve error handling in lock() (xabbuh) + * bug #22718 [Console] Fixed different behaviour of key and value user inputs in multiple choice question (borNfreee) + * bug #22901 Fix missing abstract key in XmlDumper (weaverryan) + * bug #22817 [PhpUnitBridge] optional error handler arguments (xabbuh) + * bug #22752 Improved how profiler errors are displayed on small screens (javiereguiluz) + * bug #22647 [VarDumper] Fix dumping of non-nested stubs (nicolas-grekas) + * bug #22584 [Security] Avoid unnecessary route lookup for empty logout path (ro0NL) + * bug #22690 [Console] Fix errors not rethrown even if not handled by console.error listeners (chalasr) + * bug #22669 [FrameworkBundle] AbstractConfigCommand: do not try registering bundles twice (ogizanagi) + * bug #22676 [FrameworkBundle] Adding the extension XML (flug) + +* 2.8.20 (2017-05-01) + + * bug #22550 Allow Upper Case property names in ObjectNormalizer (insekticid) + * bug #22528 [Asset] Starting slash should indicate no basePath wanted (weaverryan) + * bug #22541 [EventDispatcher] fix: unwrap listeners for correct info (dmaicher) + * bug #22526 [Asset] Preventing the base path or absolute URL from being prefixed incorrectly (weaverryan) + * bug #22523 [WebProfilerBundle] Fixed the flickering when loading complex profiler panels (javiereguiluz) + * bug #22435 [Console] Fix dispatching throwables from ConsoleEvents::COMMAND (nicolas-grekas) + * bug #22478 [Serializer] XmlEncoder: fix negative int and large numbers handling (dunglas) + * bug #22424 [Debug] Set exit status to 255 on error (nicolas-grekas) + * bug #22426 [PropertyInfo] Prevent returning int values in some cases (dunglas) + * bug #22399 Prevent double registrations related to tag priorities (nicolas-grekas) + * bug #22396 Prevent double registrations related to tag priorities (nicolas-grekas) + * bug #22352 [HttpFoundation] Add `use_strict_mode` in validOptions for session (sstok) + * bug #22351 [Yaml] don't keep internal state between parser runs (xabbuh) + * bug #22307 [Debug] Fix php notice (enumag) + * bug #22311 [DI] Fix second auto-registration (nicolas-grekas) + * bug #22109 [Validator] check for empty host when calling checkdnsrr() (apetitpa) + * bug #22280 [DI] Fix the xml schema (GuilhemN) + * bug #22282 [DI] Prevent AutowirePass from triggering irrelevant deprecations (chalasr) + * bug #22255 [Translation] avoid creating cache files for fallback locales. (aitboudad) + * bug #22292 Fixes #22264 - add support for Chrome headless (redthor) + +* 2.8.19 (2017-04-05) + + * bug #22265 Allow Upper Case property names (insekticid) + * bug #22258 [DI] Autowiring and factories are incompatible with each others (nicolas-grekas) + * bug #22254 [DI] Don't use auto-registered services to populate type-candidates (nicolas-grekas) + * bug #22229 [ExpressionLanguage] Provide the expression in syntax errors (k0pernikus, stof) + * bug #22251 [PropertyInfo] Support nullable array or collection (4rthem) + * bug #22240 [DI] Fix fatal error at ContainerBuilder::compile() if config is not installed (chalasr) + * bug #22140 [Form] Improve the exceptions when trying to get the data in a PRE_SET_DATA listener and the data has not already been set (fancyweb) + * bug #22217 [Console] Fix table cell styling (ro0NL) + * bug #22194 [Console] CommandTester: disable color support detection (julienfalque) + * bug #22188 [Console] Revised exception rendering (ro0NL) + * bug #22154 [WebProfilerBundle] Normalize whitespace in exceptions passed in headers (curry684) + * bug #22142 [Console] Escape exception messages in renderException (chalasr) + * bug #22172 Fix port usage in server:status command (alcaeus) + * bug #22164 [Bridge\Doctrine] Fix change breaking doctrine-bundle test suite (nicolas-grekas) + * bug #22133 [Filesystem] normalize paths before making them relative (xabbuh) + * bug #22138 [HttpFoundation][bugfix] $bags should always be initialized (MacDada) + * bug #21810 #21809 [SecurityBundle] bugfix: if security provider's name contains upper cases then container didn't compile (Antanas Arvasevicius) + * bug #22123 [WebProfilerBundle] Fix for CSS attribute at Profiler Translation Page (e-moe) + * bug #19778 [Security] Fixed roles serialization on token from user object (eko) + * bug #22036 Set Date header in Response constructor already (mpdude) + * bug #22022 [Validator] fix URL validator to detect non supported chars according to RFC 3986 (e-moe) + * bug #21849 [HttpFoundation] Fix missing handling of for/host/proto info from "Forwarded" header (nicolas-grekas) + * bug #21968 Fixed pathinfo calculation for requests starting with a question mark. (syzygymsu) + * bug #22027 Revert "bug #21841 [Console] Do not squash input changes made from console.command event (chalasr)" (chalasr) + * bug #21846 [HttpFoundation] Fix Request::getHost() when having several hosts in X_FORWARDED_HOST (nicolas-grekas) + * bug #21208 [Validator] Add object handling of invalid constraints in Composite (SenseException) + * bug #22044 [Serializer] [XML] Ignore Process Instruction (jordscream) + * bug #22079 [HttpKernel] Fixed bug with purging of HTTPS URLs (ausi) + * bug #21523 #20411 fix Yaml parsing for very long quoted strings (RichardBradley) + * bug #22001 [Doctrine Bridge] fix priority for doctrine event listeners (dmaicher) + * bug #21981 [Console] Use proper line endings in BufferedOutput (julienfalque) + * bug #21976 [VarDumper] Add missing isset() checks in some casters (nicolas-grekas) + * bug #21957 [Form] Choice type int values (BC Fix) (mcfedr) + * bug #21923 [travis] Test with hhvm 3.18 (nicolas-grekas) + * bug #21823 dumpFile(), preserve existing file permissions (chs2) + * bug #21865 [Security] context listener: hardening user provider handling (xabbuh) + * bug #21883 [HttpKernel] fix Kernel name when stored in a directory starting with a number (fabpot) + +* 2.8.18 (2017-03-06) + + * bug #21841 [Console] Do not squash input changes made from console.command event (chalasr) + * bug #21671 [Serializer] Xml encoder throws exception for valid data (gr1ev0us) + * bug #21805 Provide less state in getRequestFormat (dawehner) + * bug #21832 [Routing] Ignore hidden directories when loading routes from annotations (jakzal) + * bug #21769 [Form] Improve rounding precision (foaly-nr1) + * bug #21825 [PhpUnitBridge] disable global test listener when not registered (xabbuh) + * bug #21267 [Form] Fix ChoiceType to ensure submitted data is not nested unnecessarily (issei-m) + * bug #21731 Fix emacs link (rubenrua) + * bug #21800 Fix issues reported by static analyze (romainneutron) + * bug #21798 Revert "bug #21791 [SecurityBundle] only pass relevant user provider (xabbuh)" (xabbuh) + * bug #21791 [SecurityBundle] only pass relevant user provider (xabbuh) + * bug #21787 [PhpUnitBridge] do not register the test listener twice (xabbuh) + * bug #21756 [Yaml] Stop replacing NULLs when merging (gadelat) + * bug #21689 [WebServerBundle] fixed html attribute escape (Seb33300) + * bug #21722 [ExpressionLanguage] Registering functions after calling evaluate(), compile() or parse() is not supported (maidmaid) + * bug #21679 [SecurityBundle] fix priority ordering of security voters (xabbuh) + * bug #21115 [Validator] do not guess getter method names (xabbuh) + * bug #21670 [DependencyInjection] Fix autowiring types when there are more than 2 services colliding (GuilhemN) + * bug #21665 [DependencyInjection] Fix autowiring collisions detection (nicolas-grekas, GuilhemN) + * bug #21661 Fix Composer constraints (fabpot) + * bug #21582 [HttpCache] purge both http and https from http cache (dbu) + * bug #21637 [FrameworkBundle] remove translation data collector when not usable (xabbuh) + * bug #21634 [VarDumper] Added missing persistent stream cast (lyrixx) + * bug #21436 [DependencyInjection] check for circular refs caused by method calls (xabbuh) + * bug #21400 [Serializer] fix upper camel case conversion (see #21399) (markusu49) + * bug #21599 [Console][Table] fixed render when using multiple rowspans. (aitboudad) + * bug #21613 [Process] Permit empty suffix on Windows (Bilge) + * bug #21057 [DI] Auto register extension configuration classes as a resource (ro0NL) + * bug #21592 [Validator] property constraints can be added in child classes (angelk, xabbuh) + * bug #21458 [Config] Early return for DirectoryResource (robfrawley) + * bug #21562 [DoctrineBridge] make sure that null can be the invalid value (xabbuh) + * bug #21584 [WebProfilerBundle] Readd Symfony version status in the toolbar (wouterj) + * bug #21557 [VarDumper] Improve dump of AMQP* Object (lyrixx) + * bug #21542 [VarDumper] Fixed dumping of terminated generator (lyrixx) + +* 2.8.17 (2017-02-06) + + * bug #20844 [Config] Fix checking cache for non existing meta file (hason) + * bug #21063 [Form] Fixed DateType format option for single text widget (HeahDude) + * bug #21430 Casting TableCell value to string. (jaydiablo) + * bug #21359 [FrameworkBundle] fixed custom domain for translations in php templates (robinlehrmann) + * bug #21485 [Process] Non ASCII characters disappearing during the escapeshellarg (GuillaumeVerdon) + * bug #21370 [FrameworkBundle] Execute the PhpDocExtractor earlier (GuilhemN) + * bug #21462 [BrowserKit] ignore invalid cookies expires date format (xabbuh) + * bug #21438 [Console] Fix TableCell issues with decoration (ogizanagi) + * bug #21431 [DoctrineBridge] always check for all fields to be mapped (xabbuh) + * bug #21360 [PropertyAccess] Handle interfaces in the invalid argument exception (fancyweb) + * bug #21403 [DI] Fix defaults overriding empty strings in AutowirePass (nicolas-grekas) + * bug #21401 [Debug] Workaround "null" $context (nicolas-grekas) + * bug #21333 [HttpKernel] Fix ArgumentValueResolver for arguments default null (chalasr) + * bug #20871 [HttpKernel] Give higher priority to adding request formats (akeeman) + * bug #21332 [PropertyInfo] Don't try to access a property thru a static method (dunglas) + * bug #21331 [PropertyInfo] Exclude static methods form properties guessing (dunglas) + * bug #21285 [TwigBundle] do not lose already set method calls (xabbuh) + * bug #21279 #20411 fix Yaml parsing for very long quoted strings (RichardBradley) + +* 2.8.16 (2017-01-12) + + * bug #21218 [Form] DateTimeToLocalizedStringTransformer does not use timezone when using date only (magnetik) + * bug #21104 [FrameworkBundle] fix IPv6 address handling in server commands (xabbuh) + * bug #20793 [Validator] Fix caching of constraints derived from non-serializable parents (uwej711) + * bug #19586 [TwigBundle] Fix bug where namespaced paths don't take parent bundles in account (wesleylancel) + * bug #21237 [FrameworkBundle] Fix relative paths used as cache keys (nicolas-grekas) + * bug #21183 [Validator] respect groups when merging constraints (xabbuh) + * bug #21179 [TwigBundle] Fixing regression in TwigEngine exception handling (Bertalan Attila) + * bug #21220 [DI] Fix missing new line after private alias (ogizanagi) + * bug #21211 Classloader tmpname (lyrixx) + * bug #21205 [TwigBundle] fixed usage when Templating is not installed (fabpot) + * bug #21155 [Validator] Check cascasdedGroups for being countable (scaytrase) + * bug #21200 [Filesystem] Check that directory is writable after created it in dumpFile() (chalasr) + * bug #21113 [FrameworkBundle][HttpKernel] Fix resources loading for bundles with custom structure (chalasr) + * bug #21084 [Yaml] handle empty lines inside unindented collection (xabbuh) + * bug #20925 [HttpFoundation] Validate/cast cookie expire time (ro0NL) + * bug #21032 [SecurityBundle] Made collection of user provider unique when injecting them to the RemberMeService (lyrixx) + * bug #21078 [Console] Escape default value when dumping help (lyrixx) + * bug #21076 [Console] OS X Can't call cli_set_process_title php without superuser (ogizanagi) + * bug #20900 [Console] Descriptors should use Helper::strlen (ogizanagi) + * bug #21064 [Debug] Wrap call to ->log in a try catch block (lyrixx) + * bug #21010 [Debug] UndefinedMethodFatalErrorHandler - Handle anonymous classes (SpacePossum) + * bug #20859 Avoid warning in PHP 7.2 because of non-countable data (wouterj) + * bug #21053 [Validator] override property constraints in child class (xabbuh) + * bug #21034 [FrameworkBundle] Make TemplateController working without the Templating component (dunglas) + * bug #20970 [Console] Fix question formatting using SymfonyStyle::ask() (chalasr, ogizanagi) + * bug #20975 [Form] fix group sequence based validation (xabbuh) + * bug #20599 [WebProfilerBundle] Display multiple HTTP headers in WDT (ro0NL) + * bug #20799 [TwigBundle] do not try to register incomplete definitions (xabbuh) + * bug #20961 [Validator] phpize default option values (xabbuh) + * bug #20934 [FrameworkBundle] Fix PHP form templates on translatable attributes (ro0NL) + * bug #20957 [FrameworkBundle] test for the Validator component to be present (xabbuh) + * bug #20936 [DependencyInjection] Fix on-invalid attribute type in xsd (ogizanagi) + * bug #20931 [VarDumper] Fix dumping by-ref variadics (nicolas-grekas) + * bug #20734 [Security] AbstractVoter->supportsAttribute gives false positive if attribute is zero (0) (martynas-foodpanda) + * bug #14082 [config] Fix issue when key removed and left value only (zerustech) + * bug #20847 [Console] fixed BC issue with static closures (araines) + +* 2.8.15 (2016-12-13) + + * bug #20714 [FrameworkBundle] Fix unresolved parameters from default configs in debug:config (chalasr) + * bug #20442 [FrameworkBundle] Bundle commands are not available via find() (julienfalque) + * bug #20840 [WebProfilerBundle] add dependency on Twig (xabbuh) + * bug #20828 [Validator] Fix init of YamlFileLoader::$classes for empty files (nicolas-grekas) + * bug #20539 Cast result to int before adding to it (alcaeus) + * bug #20831 [Twig] Fix deprecations with Twig 1.29 (nicolas-grekas) + * bug #20767 [Cache] Fix dumping SplDoublyLinkedList iter mode (nicolas-grekas) + * bug #20736 [Console] fixed PHP7 Errors when not using Dispatcher (keradus) + * bug #20755 [HttpKernel] Regression test for missing controller arguments (iltar) + * bug #20418 [Form][DX] FileType "multiple" fixes (yceruto) + * bug #19902 [DependencyInjection] PhpDumper.php: hasReference() shouldn't search references in lazy service. (antanas-arvasevicius) + * bug #20704 [Console] Fix wrong handling of multiline arg/opt descriptions (ogizanagi) + * bug #20712 [TwigBundle] Fix twig loader registered twice (ogizanagi) + * bug #20716 [WebProfilerBundle] Fix dump block is unfairly restrained (ogizanagi) + * bug #20671 [Config] ConfigCache::isFresh() should return false when unserialize() fails (nicolas-grekas) + * bug #20676 [ClassLoader] Use only forward slashes in generated class map (nicolas-grekas) + * bug #20664 [Validator] ensure the proper context for nested validations (xabbuh) + * bug #20661 bug #20653 [WebProfilerBundle] Profiler includes ghost panels (jzawadzki) + * bug #20374 [FrameworkBundle] Improve performance of ControllerNameParser (enumag) + * bug #20474 [Routing] Fail properly when a route parameter name cannot be used as a PCRE subpattern name (fancyweb) + * bug #20566 [DI] Initialize properties before method calls (ro0NL) + * bug #20609 [DI] Fixed custom services definition BC break introduced in ec7e70fb… (kiler129) + * bug #20598 [DI] Aliases should preserve the aliased invalid behavior (nicolas-grekas) + * bug #20602 [HttpKernel] Revert BC breaking change of Request::isMethodSafe() (nicolas-grekas) + * bug #20499 [Doctrine][Form] support large integers (xabbuh) + * bug #20576 [Process] Do feat test before enabling TTY mode (nicolas-grekas) + +* 2.8.14 (2016-11-21) + + * bug #20543 [DI] Fix error when trying to resolve a DefinitionDecorator (nicolas-grekas) + * bug #20544 [PhpUnitBridge] Fix time-sensitive tests that use data providers (julienfalque) + * bug #20484 bumped min version of Twig to 1.28 (fabpot) + * bug #20519 [Debug] Remove GLOBALS from exception context to avoid endless recursion (Seldaek) + * bug #20455 [ClassLoader] Fix ClassCollectionLoader inlining with __halt_compiler (giosh94mhz) + * bug #20307 [Form] Fix Date\TimeType marked as invalid on request with single_text and zero seconds (LuisDeimos) + * bug #20466 [Translation] fixed nested fallback catalogue using multiple locales. (aitboudad) + * bug #20465 [#18637][TranslationDebug] workaround for getFallbackLocales. (aitboudad) + * bug #20440 [TwigBridge][TwigBundle][HttpKernel] prefer getSourceContext() over getSource() (xabbuh) + * bug #20422 [Translation][fallback] add missing resources in parent catalogues. (aitboudad) + * bug #20378 [Form] Fixed show float values as choice value in ChoiceType (yceruto) + * bug #20294 Improved the design of the metrics in the profiler (javiereguiluz) + * bug #20375 [HttpFoundation][Session] Fix memcache session handler (klandaika) + * bug #20377 [Console] Fix infinite loop on missing input (chalasr) + * bug #20372 [Console] simplified code (fabpot) + * bug #20342 [Form] Fix UrlType transforms valid protocols (ogizanagi) + * bug #20292 Enhance GAE compat by removing some realpath() (nicolas-grekas) + * bug #20326 [VarDumper] Fix dumping Twig source in stack traces (nicolas-grekas) + * bug #20321 Compatibility with Twig 1.27 (xkobal) + +* 2.8.13 (2016-10-27) + + * bug #20289 Fix edge case with StreamedResponse where headers are sent twice (Nicofuma) + * bug #20267 [DependencyInjection] A decorated service should not keep the autowiring types (chalasr) + * bug #20278 [DependencyInjection] merge tags instead of completely replacing them (xabbuh) + * bug #20271 Changes related to Twig 1.27 (fabpot) + * bug #20252 Trim constant values in XmlFileLoader (lstrojny) + * bug #20253 [TwigBridge] Use non-deprecated Twig_Node::getTemplateLine() (fabpot) + * bug #20243 [WebProfilerBundle][btn-link] add `cursor: pointer` (aitboudad) + * bug #20175 [VarDumper] Fix source links with latests Twig versions (nicolas-grekas) + * bug #20235 [DomCrawler] Allow pipe (|) character in link tags when using Xpath expressions (klausi, nicolas-grekas) + * bug #20224 [Twig] removed deprecations added in Twig 1.27 (fabpot) + * bug #19478 fixed Filesystem:makePathRelative and added 2 more testcases (muhammedeminakbulut) + * bug #20218 [HttpFoundation] no 304 response if method is not cacheable (xabbuh) + * bug #20207 [DependencyInjection] move tags from decorated to decorating service (xabbuh) + * bug #20205 [HttpCache] fix: do not cache OPTIONS request (dmaicher) + * bug #20146 [Validator] Prevent infinite loop in PropertyMetadata (wesleylancel) + * bug #20184 [FrameworkBundle] Convert null prefix to an empty string in translation:update (chalasr) + * bug #20154 [PropertyInfo] Fix edge cases in ReflectionExtractor (nicolas-grekas) + * bug #19725 [Security] $attributes can be anything, but RoleVoter assumes strings (Jonatan Männchen) + * bug #20127 [HttpFoundation] JSONP callback validation (ro0NL) + * bug #20163 add missing use statement (xabbuh) + * bug #19961 [Console] Escape question text and default value in SymfonyStyle::ask() (chalasr) + * bug #20141 [Console] Fix validation of empty values using SymfonyQuestionHelper::ask() (chalasr) + * bug #20147 [FrameworkBundle] Alter container class instead of kernel name in cache:clear command (nicolas-grekas) + +* 2.8.12 (2016-10-03) + + * bug #20102 [Validator] Url validator not validating hosts ending in a number (gwkunze) + * bug #20132 Use "more entropy" option for uniqid() (javiereguiluz) + * bug #20122 [Validator] Reset constraint options (ro0NL) + * bug #20116 fixed AddConstraintValidatorsPass config (fabpot) + * bug #20078 Fix #19943 Make sure to process each interface metadata only once (lemoinem) + * bug #20080 [Form] compound forms without children should be considered rendered implicitly (backbone87) + * bug #20087 [VarDumper] Fix PHP 7.1 compat (nicolas-grekas) + * bug #20086 [VarDumper] Fix PHP 7.1 compat (nicolas-grekas) + * bug #20077 [Process] silent file operation to avoid open basedir issues (xabbuh) + * bug #20079 fixed Twig support for 1.26 and 2.0 (fabpot) + * bug #20051 Fix indexBy type extraction (lemoinem) + * bug #19951 [Finder] Trim trailing directory slash in ExcludeDirectoryFilterIterator (ro0NL) + * bug #20018 [VarDumper] Fix test (nicolas-grekas) + * bug #20011 Use UUID for error codes for Form validator. (Koc) + * bug #20010 [DX] Fixed regression when exception message swallowed when logging it. (Koc) + * bug #19983 [TwigBridge] removed Twig null nodes (deprecated as of Twig 1.25) (fabpot) + * bug #19946 [Console] Fix parsing optionnal options with empty value in argv (chalasr) + * bug #19636 [Finder] no PHP warning on empty directory iteration (ggottwald) + * bug #19923 [bugfix] [Console] Set `Input::$interactive` to `false` when command is executed with `--quiet` as verbosity level (phansys) + * bug #19811 Fixed the nullable support for php 7.1 and below (2.7, 2.8, 3.0) (iltar) + * bug #19853 [PropertyInfo] Make ReflectionExtractor compatible with ReflectionType changes in PHP 7.1 (teohhanhui) + * bug #19904 [Form] Fixed collapsed ChoiceType options attributes (HeahDude) + * bug #19908 [Config] Handle open_basedir restrictions in FileLocator (Nicofuma) + * bug #19924 [DoctrineBridge][PropertyInfo] Treat Doctrine decimal type as string (teohhanhui) + * bug #19932 Fixed bad merge (GrahamCampbell) + * bug #19922 [Yaml][TwigBridge] Use JSON_UNESCAPED_SLASHES for lint commands output (chalasr) + * bug #19928 [Validator] Update IpValidatorTest data set with a valid reserved IP (jakzal) + * bug #19813 [Console] fixed PHP7 Errors are now handled and converted to Exceptions (fonsecas72) + * bug #19879 [Form] Incorrect timezone with DateTimeLocalizedStringTransformer (mbeccati) + * bug #19878 Fix translation:update command count (tgalopin) + +* 2.8.11 (2016-09-07) + + * bug #19859 [ClassLoader] Fix ClassCollectionLoader inlining with declare(strict_types=1) (nicolas-grekas) + * bug #19780 [FrameworkBundle] Incorrect line break in exception message (500 debug page) (pedroresende) + * bug #19595 [form] lazy trans `post_max_size_message`. (aitboudad) + * bug #19870 [DI] Fix setting synthetic services on ContainerBuilder (nicolas-grekas) + * bug #19848 Revert "minor #19689 [DI] Cleanup array_key_exists (ro0NL)" (nicolas-grekas) + * bug #19842 [FrameworkBundle] Check for class existence before is_subclass_of (chalasr) + * bug #19827 [BrowserKit] Fix cookie expiration on 32 bit systems (jameshalsall) + +* 2.8.10 (2016-09-02) + + * bug #19786 Update profiler's layout to use flexbox (javiereguiluz) + * bug #19794 [VarDumper] Various minor fixes & cleanups (nicolas-grekas) + * bug #19751 Fixes the calendar in constructor to handle null (wakqasahmed) + * bug #19388 [Validator][GroupSequence] fixed GroupSequence validation ignores PropetyMetadata of parent classes (Sandro Hopf) + * bug #19601 [FrameworkBundle] Added friendly exception when constraint validator class does not exist (yceruto) + * bug #19580 [Validator] fixed duplicate constraints with parent class interfaces (dmaicher) + * bug #19647 [Debug] Swap dumper services at bootstrap (lyrixx) + * bug #19685 [DI] Include dynamic services in alternatives (ro0NL) + * bug #19702 [Debug][HttpKernel][VarDumper] Prepare for committed 7.2 changes (aka "small-bc-breaks") (nicolas-grekas) + * bug #19704 [DependencyInjection] PhpDumper::isFrozen inconsistency (allflame) + * bug #19643 [DependencyInjection] Fix service autowiring inheritance (chalasr) + * bug #19667 [SecurityBundle] Add missing deprecation notice for form_login.intention (ro0NL) + * bug #19666 Verify explicitly that the request IP is a valid IPv4 address (nesk) + * bug #19660 Disable CLI color for Windows 10 greater than 10.0.10586 (mlocati) + * bug #19663 Exception details break the layout (Dionysis Arvanitis) + * bug #19651 [HttpKernel] Fix HttpCache validation HTTP method (tgalopin) + * bug #19623 [VarDumper] Fix dumping continuations (nicolas-grekas) + * bug #19549 [HttpFoundation] fixed Request::getContent() reusage bug (1ma) + * bug #19373 [Form] Skip CSRF validation on form when POST max size is exceeded (jameshalsall) + * bug #19541 Fix #19531 [Form] DateType fails parsing when midnight is not a valid time (mbeccati) + * bug #19579 [Process] Strengthen Windows pipe files opening (again...) (nicolas-grekas) + * bug #19564 Added class existence check if is_subclass_of() fails in compiler passes (SCIF) + * bug #19522 [SwiftMailerBridge] Fix flawed deprecation message (chalasr) + * bug #19510 [Process] Fix double-fread() when reading unix pipes (nicolas-grekas) + * bug #19508 [Process] Fix AbstractPipes::write() for a situation seen on HHVM (at least) (nicolas-grekas) + +* 2.8.9 (2016-07-30) + + * bug #19470 undefined offset fix (#19406) (ReenExe) + * bug #19300 [HttpKernel] Use flock() for HttpCache's lock files (mpdude) + * bug #19428 [Process] Fix write access check for pipes on Windows (nicolas-grekas) + * bug #19439 [DependencyInjection] Fixed deprecated default message template with XML (jeremyFreeAgent) + * bug #19397 [HttpFoundation] HttpCache refresh stale responses containing an ETag (maennchen) + * bug #19426 [Form] Fix the money form type render with Bootstrap3 (Th3Mouk) + * bug #19422 [DomCrawler] Inherit the namespace cache in subcrawlers (stof) + * bug #19425 [BrowserKit] Uppercase the "GET" method in redirects (jakzal) + * bug #19384 Fix PHP 7.1 related failures (nicolas-grekas) + * bug #19379 [VarDumper] Fix for PHP 7.1 (nicolas-grekas) + * bug #19342 Added class existence check if is_subclass_of() fails in compiler passes (SCIF) + * bug #19369 Fix the DBAL session handler version check for Postgresql (stof) + * bug #19368 [VarDumper] Fix dumping jsons casted as arrays (nicolas-grekas) + * bug #19334 [Security] Fix the retrieval of the last username when using forwarding (stof) + * bug #19321 [HttpFoundation] Add OPTIONS and TRACE to the list of safe methods (dunglas) + * bug #19317 [BrowserKit] Update Client::getAbsoluteUri() for query string only URIs (georaldc) + * bug #19298 [ClassLoader] Fix declared classes being computed when not needed (nicolas-grekas) + * bug #19316 [Validator] Added additional MasterCard range to the CardSchemeValidator (Dennis Væversted) + * bug #19290 [HttpKernel] fixed internal subrequests having an if-modified-since-header (MalteWunsch) + * bug #19307 [Security] Fix deprecated usage of DigestAuthenticationEntryPoint::getKey() in DigestAuthenticationListener (Maxime STEINHAUSSER) + * bug #19309 [DoctrineBridge] added missing error code for constraint. (Koc) + * bug #19306 [Form] fixed bug - name in ButtonBuilder (cheprasov) + * bug #19292 [varDumper] Fix missing usage of ExceptionCaster::$traceArgs (nicolas-grekas) + * bug #19288 [VarDumper] Fix indentation trimming in ExceptionCaster (nicolas-grekas) + * bug #19267 [Validator] UuidValidator must accept a Uuid constraint. (hhamon) + * bug #19186 Fix for #19183 to add support for new PHP MongoDB extension in sessions. (omanizer) + * bug #19253 [Console] Fix block() padding formatting after #19189 (chalasr) + * bug #19218 [Security][Guard] check if session exist before using it (pasdeloup) + +* 2.8.8 (2016-06-30) + + * bug #19217 [HttpKernel] Inline ValidateRequestListener logic into HttpKernel (nicolas-grekas) + * bug #18688 [HttpFoundation] Warning when request has both Forwarded and X-Forwarded-For (magnusnordlander) + * bug #19173 [Console] Decouple SymfonyStyle from TableCell (ro0NL) + * bug #19189 [Console] Fix formatting of SymfonyStyle::comment() (chalasr) + * bug #19211 [Form] fix post max size translation type extension for >= 2.8 (Tobion) + * bug #17822 [WIP] [Form] fix `empty_data` option in expanded `ChoiceType` (HeahDude) + * bug #19134 Distinguish between first and subsequent progress bar displays (rquadling) + * bug #19061 [FORM] fix post_max_size_message translation (alt. 2) (David Badura) + * bug #19100 [Console] Fixed SymfonyQuestionHelper multi-choice with defaults (sstok) + * bug #18924 [DoctrineBridge] Don't use object IDs in DoctrineChoiceLoader when passing a value closure (webmozart) + * bug #19138 [DomCrawler] No more exception on field name with strange format (guiled, fabpot) + * bug #18935 [Form] Consider a violation even if the form is not submitted (egeloen) + * bug #19127 [Form] Add exception to FormRenderer about non-unique block names (enumag) + * bug #19118 [Process] Fix pipes cleaning on Windows (nicolas-grekas) + * bug #19128 Avoid phpunit 5.4 warnings on getMock (2.7+) (iltar) + * bug #19114 [HttpKernel] Dont close the reponse stream in debug (nicolas-grekas) + * bug #19101 [Session] fix PDO transaction aborted under PostgreSQL (Tobion) + * bug #18501 [HttpFoundation] changed MERGE queries (hjkl) + * bug #19062 [HttpFoundation] Fix UPSERT for PgSql >= 9.5 (nicolas-grekas) + * bug #18548 [Form] minor fixes in DateTime transformers (HeahDude) + * bug #18732 [PropertyAccess][DX] Enhance exception that say that some methods are missing if they don't (nykopol) + * bug #19048 [HttpFoundation] Use UPSERT for sessions stored in PgSql >= 9.5 (nicolas-grekas) + * bug #19038 Fix feature detection for IE (Alsciende) + * bug #18915 [DependencyInjection] force enabling the external XML entity loaders (xabbuh) + * bug #19020 [Form] Fixed collapsed choice attributes (HeahDude) + * bug #19028 [Yaml] properly count skipped comment lines (xabbuh) + * bug #19009 [WebProfilerBundle] Fix invalid CSS style (romainneutron) + * bug #17733 [Yaml] Fix wrong line number when comments are inserted in the middle of a block. (paradajozsef) + * bug #18911 Fixed singular of committee (peterrehm) + * bug #18971 Do not inject web debug toolbar on attachments (peterrehm) + +* 2.8.7 (2016-06-06) + + * bug #18908 [DependencyInjection] force enabling the external XML entity loaders (xabbuh) + * bug #18893 [DependencyInjection] Skip deep reference check for 'service_container' (RobertMe) + * bug #18812 Catch \Throwable (fprochazka) + * bug #18821 [Form] Removed UTC specification with timestamp (francisbesset) + * bug #18861 Fix for #18843 (inso) + * bug #18889 [Console] SymfonyStyle: Fix alignment/prefixing of multi-line comments (chalasr) + * bug #18907 [Routing] Fix the annotation loader taking a class constant as a beginning of a class name (jakzal, nicolas-grekas) + * bug #18879 [Console] SymfonyStyle: Align multi-line/very-long-line blocks (chalasr) + * bug #18864 [Console][DX] Fixed ambiguous error message when using a duplicate option shortcut (peterrehm) + * bug #18883 Fix js comment in profiler (linnaea) + * bug #18844 [Yaml] fix exception contexts (xabbuh) + * bug #18840 [Yaml] properly handle unindented collections (xabbuh) + * bug #18765 Catch \Throwable (fprochazka) + * bug #18813 Catch \Throwable (fprochazka) + * bug #18839 People - person singularization (Keeo) + * bug #18828 [Yaml] chomp newlines only at the end of YAML documents (xabbuh) + * bug #18814 Fixed server status command when port has been omitted (peterrehm) + * bug #18799 Use levenshtein level for better Bundle matching (j0k3r) + * bug #18413 [WebProfilerBundle] Fix CORS ajax security issues (romainneutron) + * bug #18774 [console][table] adjust width of colspanned cell. (aitboudad) + * bug #18507 [BUG] Delete class 'control-group' in bootstrap 3 (Philippe Degeeter) + * bug #18747 [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m) + * bug #18635 [Console] Prevent fatal error when calling Command::getHelper without helperSet (chalasr) + * bug #18686 [console][table] adjust width of colspanned cell. (aitboudad) + * bug #18761 [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m) + * bug #18737 [Debug] Fix fatal error handlers on PHP 7 (nicolas-grekas) + +* 2.8.6 (2016-05-09) + + * security #18736 Fixed issue with blank password with Ldap (csarrazi) + * security #18733 limited the maximum length of a submitted username (fabpot) + * bug #18730 [FrameworkBundle] prevent calling get() for service_container service (xabbuh) + * bug #18705 added a conflict between Monolog bridge 2.8 and HTTP Kernel 3.0+ (fabpot) + * bug #18709 [DependencyInjection] top-level anonymous services must be public (xabbuh) + * bug #18388 [EventDispatcher] check for method to exist (xabbuh) + * bug #18699 [DependencyInjection] Use the priority of service decoration on service with parent (hason) + * bug #18692 add @Event annotation for KernelEvents (Haehnchen) + * bug #18246 [DependencyInjection] fix ambiguous services schema (backbone87) + +* 2.8.5 (2016-04-29) + + * bug #18180 [Form] fixed BC break with pre selection of choices with `ChoiceType` and its children (HeahDude) + * bug #18645 [Console] Fix wrong exceptions being thrown (JhonnyL) + * bug #18562 [WebProfilerBunde] Give an absolute url in case the request occured from another domain (romainneutron) + * bug #18600 [DI] Fix AutowirePass fatal error with classes that have non-existing parents (hason, nicolas-grekas) + * bug #18603 [PropertyAccess] ->getValue() should be read-only (nicolas-grekas) + * bug #18593 [VarDumper] Fix dumping type hints for non-existing parent classes (nicolas-grekas) + * bug #18596 [DI] Fix internal caching in AutowirePass (nicolas-grekas) + * bug #18581 [Console] [TableHelper] make it work with SymfonyStyle. (aitboudad) + * bug #18280 [Routing] add query param if value is different from default (Tobion) + * bug #18540 Replace iconv_*() uses by mb_*(), add mbstring polyfill when required (nicolas-grekas) + * bug #18496 [Console] use ANSI escape sequences in ProgressBar overwrite method (alekitto) + * bug #18490 [LDAP] Free the search result after a search to free memory (hiddewie) + * bug #18491 [DependencyInjection] anonymous services are always private (xabbuh) + * bug #18515 [Filesystem] Better error handling in remove() (nicolas-grekas) + * bug #18360 [PropertyInfo] Extract nullable and collection key type for Doctrine associations (teohhanhui) + * bug #18449 [PropertyAccess] Fix regression (nicolas-grekas) + * bug #18429 [Console] Correct time formatting. (camporter) + * bug #18457 [WebProfilerBundle] Fixed error from unset twig variable (simonsargeant) + * bug #18467 [DependencyInjection] Resolve aliases before removing abstract services + add tests (nicolas-grekas) + * bug #18469 Force profiler toolbar svg display (pyrech) + * bug #18460 [DomCrawler] Fix select option with empty value (Matt Wells) + * bug #18425 [Security] Fixed SwitchUserListener when exiting an impersonation with AnonymousToken (lyrixx) + * bug #18317 [Form] fix "prototype" not required when parent form is not required (HeahDude) + * bug #18439 [Logging] Add support for Firefox (43+) in ChromePhpHandler (arjenm) + * bug #18385 Detect CLI color support for Windows 10 build 10586 (mlocati) + * bug #18426 [EventDispatcher] Try first if the event is Stopped (lyrixx) + * bug #18407 Fixed the "hover" state of the profiler sidebar menu (javiereguiluz) + * bug #18394 [FrameworkBundle] Return the invokable service if its name is the class name (dunglas) + * bug #18347 Fixed the styles of the Symfony icon in the web debug toolbar (javiereguiluz) + * bug #18265 Optimize ReplaceAliasByActualDefinitionPass (ajb-in) + * bug #18349 [Process] Fix stream_select priority when writing to stdin (nicolas-grekas) + * bug #18358 [Form] NumberToLocalizedStringTransformer should return floats when possible (nicolas-grekas) + * bug #17926 [DependencyInjection] Enable alias for service_container (hason) + * bug #18352 [Debug] Fix case sensitivity checks (nicolas-grekas) + * bug #18336 [Debug] Fix handling of php7 throwables (nicolas-grekas) + * bug #18354 [FrameworkBundle][TwigBridge] fix high deps tests (xabbuh) + * bug #18312 [ClassLoader] Fix storing not-found classes in APC cache (nicolas-grekas) + +* 2.8.4 (2016-03-27) + + * bug #18298 [Validator] do not treat payload as callback (xabbuh) + * bug #18275 [Form] Fix BC break introduced in #14403 (HeahDude) + * bug #18271 [FileSystem] Google app engine filesystem (swordbeta) + * bug #18255 [HttpFoundation] Fix support of custom mime types with parameters (Ener-Getick) + * bug #18272 [Bridge\PhpUnit] Workaround old phpunit bug, no colors in weak mode, add tests (nicolas-grekas) + * bug #18259 [PropertyAccess] Backport fixes from 2.7 (nicolas-grekas) + * bug #18261 [PropertyAccess] Fix isPropertyWritable not using the reflection cache (nicolas-grekas) + * bug #18224 [PropertyAccess] Remove most ref mismatches to improve perf (nicolas-grekas) + * bug #18237 [WebProfilerBundle] Added table-layout property to AJAX toolbar css (kevintweber) + * bug #18209 [PropertyInfo] Support Doctrine custom mapping type in DoctrineExtractor (teohhanhui) + * bug #18210 [PropertyAccess] Throw an UnexpectedTypeException when the type do not match (dunglas, nicolas-grekas) + * bug #18216 [Intl] Fix invalid numeric literal on PHP 7 (nicolas-grekas) + * bug #18147 [Validator] EmailValidator cannot extract hostname if email contains multiple @ symbols (natechicago) + * bug #18023 [Process] getIncrementalOutput should work without calling getOutput (romainneutron) + * bug #18175 [Translation] Add support for fuzzy tags in PoFileLoader (nud) + * bug #18179 [Form] Fix NumberToLocalizedStringTransformer::reverseTransform with big integers (ovrflo, nicolas-grekas) + * bug #18164 [HttpKernel] set s-maxage only if all responses are cacheable (xabbuh) + * bug #18150 [Process] Wait a bit less on Windows (nicolas-grekas) + * bug #18130 [Debug] Replaced logic for detecting filesystem case sensitivity (Dan Blows) + * bug #18137 Autowiring the concrete class too - consistent with behavior of other services (weaverryan) + * bug #18087 [WebProfiler] Sidebar button padding (rvanlaak) + * bug #18080 [HttpFoundation] Set the Content-Range header if the requested Range is unsatisfied (jakzal) + * bug #18084 [HttpFoundation] Avoid warnings when checking malicious IPs (jakzal) + * bug #18066 [Process] Fix pipes handling (nicolas-grekas) + * bug #18078 [Console] Fix an autocompletion question helper issue with non-sequentially indexed choices (jakzal) + * bug #18048 [HttpKernel] Fix mem usage when stripping the prod container (nicolas-grekas) + * bug #18065 [Finder] Partially revert #17134 to fix a regression (jakzal) + * bug #18018 [HttpFoundation] exception when registering bags for started sessions (xabbuh) + * bug #18054 [Filesystem] Fix false positive in ->remove() (nicolas-grekas) + * bug #18049 [Validator] Fix the locale validator so it treats a locale alias as a valid locale (jakzal) + * bug #18019 [Intl] Update ICU to version 55 (jakzal) + * bug #18015 [Process] Fix memory issue when using large input streams (romainneutron) + * bug #16656 [HttpFoundation] automatically generate safe fallback filename (xabbuh) + * bug #15794 [Console] default to stderr in the console helpers (alcohol) + * bug #17984 Allow to normalize \Traversable when serializing xml (Ener-Getick) + * bug #17434 Improved the error message when a template is not found (rvanginneken, javiereguiluz) + * bug #17687 Improved the error message when using "@" in a decorated service (javiereguiluz) + * bug #17744 Improve error reporting in router panel of web profiler (javiereguiluz) + * bug #17894 [FrameworkBundle] Fix a regression in handling absolute template paths (jakzal) + * bug #17990 [DoctrineBridge][Form] Fix performance regression in EntityType (kimlai) + * bug #17595 [HttpKernel] Remove _path from query parameters when fragment is a subrequest (cmenning) + * bug #17986 [DomCrawler] Dont use LIBXML_PARSEHUGE by default (nicolas-grekas) + * bug #17668 add 'guid' to list of exception to filter out (garak) + * bug #17615 Ensure backend slashes for symlinks on Windows systems (cpsitgmbh) + * bug #17626 Try to delete broken symlinks (IchHabRecht) + * bug #17978 [Yaml] ensure dump indentation to be greather than zero (xabbuh) + * bug #16886 [Form] [ChoiceType] Prefer placeholder to empty_value (boite) + * bug #17976 [WebProfilerBundle] fix debug toolbar rendering by removing inadvertently added links (craue) + * bug #17971 Variadic controller params (NiR-, fabpot) + * bug #17876 [DependencyInjection] Fixing autowiring bug when some args are set (weaverryan) + * bug #17568 Improved Bootstrap form theme for hidden fields (javiereguiluz) + * bug #17561 [WebProfilerBundle] Fix design issue in profiler when having errors in forms (Pierstoval) + * bug #17925 [Bridge] The WebProcessor now forwards the client IP (magnetik) + +* 2.8.3 (2016-02-28) + + * bug #17947 Fix - #17676 (backport #17919 to 2.3) (Ocramius) + * bug #17942 Fix bug when using an private aliased factory service (WouterJ) + * bug #17798 [Form] Fix BC break by allowing 'choice_label' option to be 'false' in ChoiceType (HeahDude) + * bug #17542 ChoiceFormField of type "select" could be "disabled" (bouland) + * bug #17602 [HttpFoundation] Fix BinaryFileResponse incorrect behavior with if-range header (bburnichon) + * bug #17760 [Form] fix choice value "false" in ChoiceType (HeahDude) + * bug #17914 [Console] Fix escaping of trailing backslashes (nicolas-grekas) + * bug #17074 Fix constraint validator alias being required (Triiistan) + * bug #17866 [DependencyInjection] replace alias in factories (xabbuh) + * bug #17867 [DependencyInjection] replace alias in factory services (xabbuh) + * bug #17860 Fixed the antialiasing of the toolbar text (javiereguiluz) + * bug #17569 [FrameworkBundle] read commands from bundles when accessing list (havvg) + * bug #16987 [FileSystem] Windows fix (flip111) + * bug #17787 [Form] Fix choice placeholder edge cases (Tobion) + * bug #17835 [Yaml] fix default timezone to be UTC (xabbuh) + * bug #17823 [DependencyInjection] fix dumped YAML string (xabbuh) + * bug #17818 [Console] InvalidArgumentException is thrown under wrong condition (robinkanters) + * bug #17819 [HttpKernel] Prevent a fatal error when DebugHandlersListener is used with a kernel with no terminateWithException() method (jakzal) + * bug #17814 [DependencyInjection] fix dumped YAML snytax (xabbuh) + * bug #17099 [Form] Fixed violation mapping if multiple forms are using the same (or part of the same) property path (alekitto) + * bug #17694 [DoctrineBridge] [Form] fix choice_value in EntityType (HeahDude) + * bug #17790 [Config] Fix EnumNodeDefinition to allow building enum nodes with one element (ogizanagi) + * bug #17729 [Yaml] properly parse lists in object maps (xabbuh) + * bug #17719 [DependencyInjection] fixed exceptions thrown by get method of ContainerBuilder (lukaszmakuch) + * bug #17742 [DependencyInjection] Fix #16461 Container::set() replace aliases (mnapoli) + * bug #17745 Added more exceptions to singularify method (javiereguiluz) + * bug #17691 Fixed (string) catchable fatal error for PHP Incomplete Class instances (yceruto) + * bug #17766 Fixed (string) catchable fatal error for PHP Incomplete Class instances (yceruto) + * bug #17757 [HttpFoundation] BinaryFileResponse sendContent return as parent. (2.3) (SpacePossum) + * bug #17748 [DomCrawler] Remove the overridden getHash() method to prevent problems when cloning the crawler (jakzal) + * bug #17725 [WebProfilerBundle] Add width attribute on SVG - Fix toolbar profiler on microsoft edge (AlexandrePavy) + * bug #17703 [FrameworkBundle] Support autowiring for TranslationInterface (dunglas) + * bug #17613 [WebProfiler] Fixed logo and menu profiler for Microsoft Edge (WhiteEagle88) + * bug #17702 [TwigBridge] forward compatibility with Yaml 3.1 (xabbuh) + * bug #17673 [Routing] add files used in FileResource objects (xabbuh) + * bug #17672 [DependencyInjection][Routing] add files used in FileResource objects (xabbuh) + * bug #17669 [Console] remove readline support (xabbuh) + * bug #17600 Fixed the Bootstrap form theme for inlined checkbox/radio (javiereguiluz) + * bug #17596 [Translation] Add resources from fallback locale to parent catalogue (c960657) + * bug #17605 [FrameworkBundle] remove default null value for asset version (xabbuh) + * bug #17606 [DependencyInjection] pass triggerDeprecationError arg to parent class (xabbuh) + * bug #16956 [DependencyInjection] XmlFileLoader: enforce tags to have a name (xabbuh) + * bug #16265 [BrowserKit] Corrected HTTP_HOST logic (Naktibalda) + * bug #17559 [SecurityBundle] Fix HTTP Digest auth not being passed user checker (SamFleming) + * bug #17554 [DependencyInjection] resolve aliases in factories (xabbuh) + * bug #17555 [DependencyInjection] resolve aliases in factory services (xabbuh) + * bug #17511 [Form] ArrayChoiceList can now deal with a null in choices (issei-m) + * bug #17430 [Serializer] Ensure that groups are strings (dunglas) + * bug #15272 [FrameworkBundle] Fix template location for PHP templates (jakzal) + * bug #11232 [Routing] Fixes fatal errors with object resources in AnnotationDirectoryLoader::supports (Tischoi) + * bug #17526 Escape the delimiter in Glob::toRegex (javiereguiluz) + * bug #17527 fixed undefined variable (fabpot) + * bug #15706 [framework-bundle] Added support for the `0.0.0.0/0` trusted proxy (zerkms) + * bug #16274 [HttpKernel] Lookup the response even if the lock was released after two second wait (jakzal) + * bug #16954 [TranslationUpdateCommand] fixed undefined resultMessage var. (aitboudad) + * bug #17355 [DoctrineBridge][Validator] >= 2.3 Pass association instead of ID as argument (xavismeh) + * bug #17330 Limit the max height/width of icons in the profiler menu (javiereguiluz) + * bug #17454 Allow absolute URLs to be displayed in the debug toolbar (javiereguiluz) + * bug #16736 [Request] Ignore invalid IP addresses sent by proxies (GromNaN) + * bug #17459 [EventDispatcher] TraceableEventDispatcher resets event listener priorities (c960657) + * bug #17486 [FrameworkBundle] Throw for missing container extensions (kix) + * bug #16961 Overriding profiler position in CSS breaks JS positioning (aschempp) + * bug #16873 Able to load big xml files with DomCrawler (zorn-v) + * bug #16897 [Form] Fix constraints could be null if not set (DZunke) + * bug #16912 [Translation][Writer] avoid calling setBackup if the dumper is not FileDumper (aitboudad) + * bug #17505 sort bundles in config:dump-reference command (xabbuh) + * bug #17514 [Asset] Add defaultNull to version configuration (ewgRa) + * bug #16511 [Asset] Ability to set empty version strategy in packages (ewgRa) + * bug #17457 Display Ajax requests from newest to oldest in the toolbar (javiereguiluz) + * bug #17503 [Asset] CLI: use request context to generate absolute URLs (xabbuh) + * bug #17478 [HttpFoundation] Do not overwrite the Authorization header if it is already set (jakzal) + * bug #17461 [Yaml] tag for dumped PHP objects must be a local one (xabbuh) + * bug #16822 [FrameworkBundle][Validator] Fix apc cache service deprecation (ogizanagi) + * bug #17463 [Form] make tests compatible with Symfony 2.8 and 3.0 (xabbuh) + * bug #17456 [DX] Remove default match from AbstractConfigCommand::findExtension (kix) + * bug #17424 [Process] Update in 2.7 for stream-based output storage (romainneutron) + * bug #17417 Fixed the form profiler when using long form types (javiereguiluz) + * bug #17423 [Process] Use stream based storage to avoid memory issues (romainneutron) + * bug #17406 [Form] ChoiceType: Fix a notice when 'choices' normalizer is replaced (paradajozsef) + * bug #17433 [FrameworkBundle] Don't log twice with the error handler (nicolas-grekas) + * bug #17418 Fixed Bootstrap form theme form "reset" buttons (javiereguiluz) + * bug #17416 [PropertyInfo] PhpDocExtractor: Fix a notice when the property doesn'… (dunglas) + * bug #17404 fix merge 2.3 into 2.7 for SecureRandom dependency (Tobion) + * bug #17373 [SecurityBundle] fix SecureRandom service constructor args (Tobion) + * bug #17382 [TwigBridge] Use label_format option for checkbox and radio labels (enumag) + * bug #17380 [TwigBridge] Use label_format option for checkbox and radio labels (enumag) + * bug #17377 Fix performance (PHP5) and memory (PHP7) issues when using token_get_all (nicolas-grekas, peteward) + * bug #17389 [Routing] Fixed correct class name in thrown exception (fixes #17388) (robinvdvleuten) + * bug #17358 [ClassLoader] Use symfony/polyfill-apcu (nicolas-grekas) + * bug #17370 [HttpFoundation][Cookie] Cookie DateTimeInterface fix (wildewouter) + +* 2.8.2 (2016-01-14) + + * security #17359 do not ship with a custom rng implementation (xabbuh, fabpot) + * bug #17253 [Console] HHVM read input stream bug (mbutkereit) + * bug #17314 Fix max width for multibyte keys in choice question (mheki) + * bug #17326 [Console] Display console application name even when no version set (polc) + * bug #17328 [Serializer] Allow to use proxies in object_to_populate (dunglas) + * bug #17202 [FrameworkBundle] Don't log twice with the error handler (nicolas-grekas) + * bug #17347 Workaround https://bugs.php.net/63206 (nicolas-grekas) + * bug #17199 [Serializer] Allow context to contain not serializable data (dunglas, nicolas-grekas) + * bug #17334 [WebProfiler] Fixed sf-minitoolbar height (yceruto) + * bug #17140 [Serializer] Remove normalizer cache in Serializer class (jvasseur) + * bug #17320 [Debug] Fixed erroneous deprecation notice for extended Interfaces (peterrehm) + * bug #17307 [FrameworkBundle] Fix paths with % in it (like urlencoded) (scaytrase) + * bug #17078 [Bridge] [Doctrine] [Validator] Added support \IteratorAggregate for UniqueEntityValidator (Disparity) + * bug #17298 [FrameworkBundle] Use proper class to fetch $versionStrategy property (dosten) + * bug #17287 [HttpKernel] Forcing string comparison on query parameters sort in UriSigner (Tim van Densen) + * bug #17279 [FrameworkBundle] Add case in Kernel directory guess for PHPUnit (tgalopin) + * bug #17278 [FrameworkBundle] Add case in Kernel directory guess for PHPUnit (tgalopin) + * bug #17283 [WebProfilerBundle] Remove loading status from AJAX toolbar after error (kucharovic) + * bug #17275 [PhpUnitBridge] Re-enable the garbage collector (nicolas-grekas) + * bug #17276 [Process] Fix potential race condition (nicolas-grekas) + * bug #17261 [FrameworkBundle] Allow to autowire service_container (dunglas) + * bug #17183 [FrameworkBundle] Set the kernel.name properly after a cache warmup (jakzal) + * bug #17197 [Yaml] cast arrays to objects after parsing has finished (xabbuh) + * bug #17247 Fix toolbar display when nvd3 is loaded on page (Seldaek) + * bug #17159 [Yaml] recognize when a block scalar is left (xabbuh) + * bug #17195 bug #14246 [Filesystem] dumpFile() non atomic (Hidde Boomsma) + * feature #16747 [Form] Improved performance of ChoiceType and its subtypes (webmozart) + * bug #17179 [WebProfiler] Removed an object as route generator argument (iltar) + * bug #17177 [Process] Fix potential race condition leading to transient tests (nicolas-grekas) + * bug #17163 [Form] fix Catchable Fatal Error if choices is not an array (Gladhon, nicolas-grekas) + * bug #17152 [DoctrineBridge] [PropertyInfo] Catch Doctrine\ORM\Mapping\MappingException (dunglas) + * bug #17119 [Form] improve deprecation message for "empty_value" and "choice_list" options. (hhamon) + * bug #17156 [HttpFoundation] add missing symfony/polyfill-php55 dependency (xabbuh) + * bug #17162 [Form] Fix regression on Collection type (hason) + +* 2.8.1 (2015-12-26) + + * bug #16864 [Yaml] fix indented line handling in folded blocks (xabbuh) + * bug #17052 Fixed flatten exception recursion with errors (GrahamCampbell) + * bug #16826 Embedded identifier support (mihai-stancu) + * bug #17079 Also transform inline mappings to objects (WouterJ) + * bug #17129 [Config] Fix array sort on normalization in edge case (romainneutron) + * feature #17035 [DomCrawler] Revert previous restriction, allow selection of every DOMNode object (EdgarPE) + * bug #17094 [Process] More robustness and deterministic tests (nicolas-grekas) + * bug #17112 [PropertyAccess] Reorder elements array after PropertyPathBuilder::replace (alekitto) + * bug #17109 Improved the design of the web debug toolbar (javiereguiluz) + * bug #16797 [Filesystem] Recursivly widen non-executable directories (Slamdunk) + * bug #16926 [DependencyInjection] fixed definition loosing property shared when decorated by a parent definition (wahler) + * bug #17040 [Console] Avoid extra blank lines when rendering exceptions (ogizanagi) + * bug #17044 [Form] fix BC break introduced with prototype_data option (memphys) + * bug #17055 [Security] Verify if a password encoded with bcrypt is no longer than 72 characters (jakzal) + * bug #16959 [Form] fix #15544 when a collection type attribute "required" is false, "prototype" should too (HeahDude) + * bug #16806 [Validator] BicValidator - fixed raising violations to a maximum of one (mvhirsch) + * bug #16842 [Ldap] Escape carriage returns in LDAP DNs. (ChadSikorra) + * bug #16860 [Yaml] do not remove "comments" in scalar blocks (xabbuh) + * bug #17002 [Console][Table] fixed render row that contains multiple cells. (aitboudad) + * bug #16964 CSS min-height and min-width should not be "auto" (aschempp) + * bug #16971 [HttpFoundation] Added the ability of using BinaryFileResponse with stream wrappers (jakzal, Sander-Toonen) + * bug #17048 Fix the logout path when not using the router (stof) + * bug #17049 Fix the logout path when not using the router (stof) + * bug #17057 [FrameworkBundle][HttpKernel] the finder is required to discover bundle commands (xabbuh) + * bug #17059 [HttpFoundation] fix error level for deprecation (xabbuh) + * bug #17006 [Form] Fix casting regression in DoctrineChoiceLoader (bendavies) + * bug #16911 [PropertyInfo] Update List Information from ReflectionExtractor (zanderbaldwin) + * bug #16955 [FrameworkBundle] ContainerDebugCommand: pass the right object to the descriptors (xabbuh) + * feature #16760 Show silenced errors in separate tab (peterrehm) + * feature #16937 [PhpUnitBridge] Replace "weak-verbose" by "deprecations upper bound" mode (nicolas-grekas) + * bug #16915 [Process] Enhance compatiblity with --enable-sigchild (nicolas-grekas) + * bug #16829 [FrameworkBundle] prevent cache:clear creating too long paths (Tobion) + * bug #16922 [FrameworkBundle] [Bug] Fixes new InputStyle bug #16920 (AlmogBaku) + * bug #16921 Fix short array syntax for php 5.3 (ewgRa) + * bug #16450 [Serializer] Fixed `array_unique` on array of objects in `getAllowedAttributes`. (CornyPhoenix) + * bug #16757 [FrameworkBundle] [Translation] Fixed translations not written when no translations directory in update command (jeremyFreeAgent) + * bug #16902 [Security] Fix a Polyfill import statement in StringUtils (magnetik) + * bug #16871 [FrameworkBundle] Disable built-in server commands when Process component is missing (gnugat, xabbuh) + * bug #16870 [FrameworkBundle] Disable the server:run command when Process component is missing (gnugat, xabbuh) + * feature #16789 [PhpUnitBridge] Add weak-verbose mode and match against message instead of test name (nicolas-grekas) + * bug #16796 [Form] Fix choices defined as Traversable (nicolas-grekas) + * bug #16742 [Console][ProgressBar] redrawFrequency should never be 0 (dritter) + * bug #16846 [MonologBridge] Monolog Bridge 2.8 is incompatible with HttpKernel 3.0 (derrabus) + * bug #16799 Improve error message for undefined DIC aliases (mpdude) + * bug #16825 [VarDumper] fix .sf-dump z-index (debug bar conflict) (Antoine LA) + * bug #16772 Refactoring EntityUserProvider::__construct() to not do work, cause cache warm error (weaverryan) + +* 2.8.0 (2015-11-30) + + * bug #16758 Fix BC for the default root form name (stof) + * bug #16753 [Process] Fix signaling/stopping logic on Windows (nicolas-grekas) + * feature #16755 [Security] add subject variable to expression context (xabbuh) + * bug #16642 [DI][autowiring] throw exception when many services use the same class. (aitboudad) + * bug #16745 [Yaml] look for colon in parsed inline string (xabbuh) + * bug #16733 [Console] do not encode backslashes in console default description (Tobion) + * feature #16735 [WIP] [Ldap] Marked the Ldap component as internal (csarrazi) + * bug #16734 Make sure security.role_hierarchy.roles always exists (WouterJ) + * feature #16722 [Security][SecurityBundle] Use csrf_token_id instead of deprecated intention (jakzal) + * bug #16312 [HttpKernel] clearstatcache() so the Cache sees when a .lck file has been released (mpdude) + * bug #16351 [WIP] [Form] [TwigBridge] Bootstrap horizontal theme missing tests (pieter2627) + * bug #16685 [Form] Fixed: Duplicate choice labels are remembered when using "choices_as_values" = false (webmozart) + * feature #16709 [Bridge\PhpUnit] Display the stack trace of a deprecation on-demand (nicolas-grekas) + * bug #16704 [Form+SecurityBundle] Trigger deprecation for csrf_provider+intention options (nicolas-grekas) + * feature #16706 [HttpFoundation] Deprecate $deep parameter on ParameterBag (nicolas-grekas) + * bug #16705 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * feature #16690 [Form] Deprecated ArrayKeyChoiceList (webmozart) + * feature #16687 [Form] Deprecated TimezoneType::getTimezones() (webmozart) + * bug #16681 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * bug #16695 [SecurityBundle] disable the init:acl command if ACL is not used (Tobion) + * bug #16677 [Form] Fixed wrong usages of the "text" type (webmozart) + * bug #16679 [Form] Disabled view data validation if "data_class" is set to null (webmozart) + * bug #16621 [Console] Fix bug with $output overloading (WouterJ) + * feature #16601 [Security] Deprecate "AbstractVoter" in favor of "Voter" (nicolas-grekas, lyrixx) + * bug #16676 [HttpFoundation] Workaround HHVM rewriting HTTP response line (nicolas-grekas) + * bug #16668 [ClassLoader] Fix parsing namespace when token_get_all() is missing (nicolas-grekas) + * bug #16386 Bug #16343 [Router] Too many Routes ? (jelte) + * bug #16498 fix unused variable warning (eventhorizonpl) + * feature #16031 [Translation][Form] Do not translate form labels and placeholders when 'translation_domain' is false (Restless-ET) + * bug #16651 [Debug] Ensure class declarations are loaded only once (nicolas-grekas) + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16633 [Filesystem] Fixed failing test due to tempdir symlink (toretto460) + * bug #16609 [HttpKernel] Don't reset on shutdown but in FrameworkBundle/Test/KernelTestCase (nicolas-grekas) + * bug #16477 [Routing] Changing RouteCollectionBuilder::import() behavior to add to the builder (weaverryan) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16377 [WebProfilerBundle] Fix minitoolbar height (rvanlaak) + * bug #16585 Add support for HTTP status code 418 back (dawehner) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16581 Fix call to undefined function json_last_error_message (dawehner) + * bug #16573 [FrameworkBundle] Fix PropertyInfo extractor namespace in framework bundle (jvasseur) + * bug #16578 [Console] Fix bug in windows detection (kbond) + * bug #16546 [Serializer] ObjectNormalizer: don't serialize static methods and props (dunglas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + +* 2.8.0-BETA1 (2015-11-16) + + * feature #16156 [Filesystem] Changed dumpFile to allow dumping to streams (markchalloner, pierredup) + * feature #16502 [Bridge\PhpUnit] Add extra clock-mocked namespaces in phpunit.xml.dist (nicolas-grekas) + * feature #16464 [DependencyInjection] Fix some edge cases with autowiring (dunglas) + * feature #16433 [Yaml] deprecate unquoted indicator characters (xabbuh) + * feature #16419 [FrameworkBundle][Form] Better exception message for private form tagged services (ogizanagi) + * feature #15990 added a micro kernel (fabpot) + * feature #16459 [Security\Core] Deprecate passing $salt to BCryptPasswordEncoder::encodePassword() (nicolas-grekas) + * feature #16409 [Console] Add progress indicator helper (kbond) + * feature #16423 [VarDumper] Deprecate VarDumperTestCase in favor of the trait (nicolas-grekas) + * feature #16424 [DI] Deprecate ContainerAware in favor of ContainerAwareTrait (nicolas-grekas) + * feature #16430 [HttpKernel] PostResponseEvent should extend the KernelEvent (jakzal) + * feature #16325 [VarDumper] Casters for Generator, ReflectionGenerator and ReflectionType (nicolas-grekas) + * feature #16395 checkCredentials() force it to be an affirmative yes! (weaverryan) + * feature #16344 [WebProfilerBundle] Filter links in search results (Rvanlaak) + * feature #16285 [Yaml] deprecated usage of @ and ` at the beginning of an unquoted string (fabpot) + * feature #16317 Rely on iconv and symfony/polyfill-* (nicolas-grekas) + * feature #15966 [FrameworkBundle] PropertyInfo support (dunglas) + * feature #16161 [Validator] Add expressionLanguage to ExpressionValidator constructor (enumag) + * feature #16263 [FrameworkBundle] Add a new ClassCache cache warmer (tucksaun) + * feature #16271 [TwigBundle] added a Twig templates warmer when templating is disabled (fabpot) + * feature #16276 Unify URL generator reference type + make linking in php templates consistent with twig (Tobion) + * feature #15947 Added UserLoaderInterface for loading users through Doctrine. (mtrojanowski) + * feature #16194 [PhpUnit] Mock clock on @group time-sensitive annotations (nicolas-grekas) + * feature #16201 [Yaml] deprecated non-escaped \ in double-quoted strings when parsing (fabpot) + * feature #16198 [EventDispatcher] added EventDispatcher::getListenerPriority() (fabpot) + * feature #15025 [2.8] [Form] Rename CollectionType options for entries (WouterJ) + * feature #16189 [PhpUnitBridge] Add SkippedTestsListener to collect and replay skipped tests (nicolas-grekas) + * feature #15879 Deprecate the SecureRandom class (pierredup) + * feature #16001 [DI] Warn when a definition relies on a deprecated class in ContainerBuilder::createService() (nicolas-grekas) + * feature #14044 [Console] [Helper] [Table] Columns styles (MAXakaWIZARD) + * feature #14908 Include working directory in ProcessFailedException (Rvanlaak) + * feature #16102 Simplify AbstractVoter (Koc) + * feature #15613 [DependencyInjection] Add autowiring capabilities (dunglas) + * feature #14721 [Security] Configuring a user checker per firewall (iltar) + * feature #16069 [WebProfilerBundle] Move AjaxCollector to HttpKernel for use with Silex (glaubinix, fabpot) + * feature #16063 [VarDumper] Add $this->getDump($var) when using VarDumperTestTrait (nicolas-grekas) + * feature #16058 Prevent adding non-DOMElement elements in DomCrawler (stof) + * feature #16057 Deprecate loading multiple documents in the same crawler (stof) + * feature #15742 Using a service as a router resource (weaverryan) + * feature #15778 Fluid interface for building routes in PHP (weaverryan) + * feature #16029 [FrameworkBundle][TwigBridge] do not render empty form action attributes (xabbuh) + * feature #15938 [Console] Bind input before executing the COMMAND event (WouterJ) + * feature #15503 UI & CSS improvement to new toolbar (WouterJ) + * feature #15838 [VarDumper] Dump PHP+Twig code excerpts in backtraces (nicolas-grekas) + * feature #16011 [FrameworkBundle] Tag deprecated services (nicolas-grekas) + * feature #15944 Remove profiler storages (javiereguiluz) + * feature #16007 [HttpFoundation] deprecate finding deep items in request parameters (xabbuh) + * feature #15978 Updated the styles of the cache commands (javiereguiluz) + * feature #15972 [Console] Updated the styles of the server commands (javiereguiluz) + * feature #15964 Symfony Console Style tweaks (javiereguiluz) + * feature #15919 [Form] Guess currency field based on validator constraint (enumag) + * feature #15934 Add a non-static API for the CssSelector component (stof) + * feature #14235 [FrameworkBundle] Refactored assets:install command and apply Symfony styles (1ed) + * feature #15963 added logging of unused tags (Marmelatze, fabpot) + * feature #15970 [TwigBundle] removed usage of Templating classes (fabpot) + * feature #14132 Applied the new styles to the router: commands (javiereguiluz) + * feature #15356 [WebProfilerBundle] Profiler View Latest should preserve all the current query parameters (jbafford) + * feature #15953 [TwigBridge] is_granted no longer raise an exception if the token storage is empty (lyrixx) + * feature #14602 [2.8] [Ldap] Added support for LDAP (New Component + integration in the Security Component). (csarrazi, lyrixx) + * feature #15939 Removed the "Delete profiles" action from the web profiler sidebar (javiereguiluz) + * feature #15962 [Finder] simplified code (fabpot) + * feature #15882 Easier Custom Authentication errors (weaverryan) + * feature #15907 [DomCrawler] Deprecate methods inherited from SplObjectStorage (stof) + * feature #15301 [Form][Type Date/Time] added choice_translation_domain option. (aitboudad) + * feature #15697 [BrowserKit] Added isFollowingRedirects and getMaxRedirects methods (Naktibalda) + * feature #15719 Deprecate ResourceInterface::getResource() (mpdude) + * feature #15818 [WebProfilerBundle] Add collapsed sidebar on small screens (hason) + * feature #15858 [PropertyInfo] Import the component (dunglas) + * feature #15892 deprecated the Shell Console class (fabpot) + * feature #15519 [Validator] added BIC (SWIFT-BIC) validation constraint (mvhirsch) + * feature #12587 [TwigBridge] Foundation form layout integration (totophe) + * feature #15151 [Security] Deprecated supportsAttribute and supportsClass methods (WouterJ) + * feature #15491 Add support for deprecated definitions (Taluu) + * feature #14894 [Console] Add domain exceptions to replace generic exceptions (GromNaN) + * feature #15738 Implement service-based Resource (cache) validation (mpdude) + * feature #14673 New Guard Authentication System (e.g. putting the joy back into security) (weaverryan) + * feature #15870 Updating AbstractVoter so that the method receives the TokenInterface (weaverryan) + * feature #15786 [Translation][File dumper] allow get file content without writing in file. (aitboudad) + * feature #15805 [Finder] Deprecate adapters and related classes (nicolas-grekas) + * feature #15837 [VarDumper] Add EnumStub for dumping virtual collections with casters (nicolas-grekas) + * feature #15699 [Translator][FileDumper] deprecated format method in favor of formatCatalogue. (aitboudad) + * feature #15717 [Translator][Loader] added XLIFF 2.0 support. (xphere, aitboudad) + * feature #15743 Validate the extended type for lazy-loaded type extensions (stof) + * feature #13761 Automatically process extensions when they implement CompilerPassInterface (WouterJ) + * feature #15787 [VarDumper] Add caster for OuterIterator objects (nicolas-grekas) + * feature #13616 [HttpKernel] Add entry point to more easily create/configure the DI extension (egeloen) + * feature #14378 [DX] Added a logout link in the security panel of the web debug toolbar (javiereguiluz) + * feature #15620 [WIP] #15502 Make template shortcuts be usable without Templating component (Koc) + * feature #15523 Redesigned the Symfony Profiler (javiereguiluz) + * feature #15773 Make the exception output visible even in quiet mode, fixes #15680 (Seldaek) + * feature #15772 Convert Output::write's type to an options arg where verbosity can be passed in as well (Seldaek) + * feature #15756 [Translation] added option json_options to JsonFileDumper. (gepo) + * feature #15724 [HttpKernel] Move required RequestStack args as first arguments (nicolas-grekas) + * feature #15521 [Debug] Add BufferingLogger for errors that happen before a proper logger is configured (nicolas-grekas) + * feature #15709 [WebProfilerBundle] deprecated import/export commands (fabpot) + * feature #15710 added ExceptionHandler::getHtml() to expose the full HTML of an exception (fabpot) + * feature #15562 [translation] Deprecated DiffOperation (zerustech) + * feature #15635 [Config] Prototypes info (ogizanagi) + * feature #15551 [Translation] added element metadata to XliffFileDumper (aitboudad) + * feature #15555 [VarDumper] Add caster for pgsql resources (nicolas-grekas) + * feature #15452 [Translator] [Xliff] Add support for target attributes. (marcosdsanchez) + * feature #15416 [DependencyInjection] Added a way to define the priority of service decoration (dosten) + * feature #15433 Allow to define Enum nodes with 1 single element (javiereguiluz) + * feature #13990 [Form] Add flexibility for EntityType (raziel057) + * feature #15382 [Console] Use readline for user input when available #DX (michaelperrin) + * feature #15013 [Security] Removed security-acl from the core (iltar) + * feature #15079 [Form] Deprecated FormTypeInterface::getName() and passing of type instances (webmozart) + * feature #15418 [Debug] Deprecate ExceptionHandler::createResponse (nicolas-grekas) + * feature #15123 [2.8][FrameworkBundle] Allow parameter use_cookies in session configuration (derrabus) + * feature #14987 [FrameworkBundle] Configurable Serializer name converter (dunglas) + * feature #15285 [Config] deprecate cannotBeEmpty() for boolean and numeric nodes (xabbuh) + * feature #15372 [FrameworkBundle] Change the default value of cookie_httponly (jderusse) + * feature #15160 Redesigned the web debug toolbar (javiereguiluz) + * feature #15185 Implement resettable containers (stof) + * feature #15131 [Security] Moved Simple{Form,Pre}AuthenticatorInterfaces to Security\Http (WouterJ) + * feature #15290 [DependencyInjection] Forbid container cloning (jakzal) + * feature #14264 [WebProfilerBundle] Add link to show profile of latest request (xelaris) + * feature #15139 [Translation] Add parameters to DataCollectorTranslator (damienalexandre) + * feature #15175 [VarDumper] Ingore PHPUnit and Prophecy object when they are nested (lyrixx) + * feature #15141 [DX] [Security] Renamed Token#getKey() to getSecret() (WouterJ) + * feature #15154 [Validator] Added missing error codes and turned codes into UUIDs (webmozart) + * feature #15096 [DependencyInjection] Allow anonymous DefinitionDecorator resolving (nicolas-grekas) + * feature #14764 [TwigBundle] Warmup twig templates in non-standard paths (kbond) + * feature #15134 [FrameworkBundle] add option to force web server startup (xabbuh) + * feature #14238 [config] added remove option to ignoreExtraKeys (twifty) + * feature #15076 [Debug] Allow throwing from __toString() with `return trigger_error($e, E_USER_ERROR);` (nicolas-grekas) + * feature #14984 [DependencyInjection] Deprecate scope concept (dosten) + * feature #14429 [FrameworkBundle] Add a doctrine cache service definition for validator mapping (jakzal) + * feature #14991 [Console][Table] allow multiple render() calls. (jaytaph) + * feature #14660 [Form] moved data trimming logic of TrimListener into StringUtil (issei-m) + * feature #15019 [Form] Deprecated "cascade_validation" (webmozart) + * feature #12314 [Form] Add "prototype_data" option to collection type (kgilden) + * feature #12067 [Form] Added the 'range' FormType (jaytaph) + * feature #14993 [Profiler][Translation] added filter. (aitboudad) + * feature #14912 [HttpFoundation] Postpone setting the date header on a Response (jakzal) + * feature #14903 [profiler][request toolbar] Removed route name from the toolbar (MJBGO) + * feature #14904 [toolbar] Merged colored icons in toolbar (MJBGO) + * feature #14781 [TwigBundle] Reconfigure twig paths when they are updated (chbruyand) + * feature #14700 [DependencyInjection] [Routing] [Config] Recursive directory loading (lavoiesl, nicolas-grekas) + * feature #14733 [Security] Add setVoters() on AccessDecisionManager (nicolas-grekas) + * feature #14756 [Serializer] Support for array denormalization (derrabus) + * feature #14630 [Translator] Dump translation constants as tree instead of simple list (gepo) + * feature #14403 [Form] deprecate read_only option (Tobion) + * feature #13324 [WebProfilerBundle] Improved page for logs (hason) + * feature #14561 [FrameworkBundle][DX] Add option to specify additional translation loading paths (Seldaek) + * feature #14563 [FrameworkBundle][EventDispatcher] Add priorities to the debug:event-dispatcher command (Seldaek) + * feature #14546 [Translator] deprecate getMessages in favor of getCatalogue. (aitboudad) + * feature #14320 [Translation] added an --all option to the debug:translation command #14237 (sgehrig) + * feature #14473 [DX] Minor improvement for the translation:debug output (nicolasdewez) + * feature #14443 [VarDumper] Allow preserving a subset of cut arrays (nicolas-grekas) + * feature #14431 [Console] Bind the closure (code) to the Command if possible (lyrixx) + * feature #14424 [VarDumper] Added support for SplFileInfo (lyrixx) + * feature #14359 [Translation] added FileLoader. (aitboudad) + * feature #14383 [FrameworkBundle][Server Command] add address port number option. (aitboudad) diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md new file mode 100644 index 0000000000000..64a31dde75600 --- /dev/null +++ b/UPGRADE-2.8.md @@ -0,0 +1,754 @@ +UPGRADE FROM 2.7 to 2.8 +======================= + +All components +-------------- + +* Symfony now requires the iconv extension to be present, which is the case by + default in most environments. However, if you're not able to ensure this + extension to be installed in your target environment, you can add Symfony's + iconv polyfill to your project's composer.json file. + + ```json + "require": { + "symfony/polyfill-iconv": "~1.0" + } + ``` + +Form +---- + + * The `intention` option was deprecated and will be removed in 3.0 in favor + of the new `csrf_token_id` option. + + * The `csrf_provider` option was deprecated and will be removed in 3.0 in favor + of the new `csrf_token_manager` option. + + * The "cascade_validation" option was deprecated. Use the "constraints" + option together with the `Valid` constraint instead. Contrary to + "cascade_validation", "constraints" must be set on the respective child forms, + not the parent form. + + Before: + + ```php + $form = $this->createFormBuilder($article, array('cascade_validation' => true)) + ->add('author', new AuthorType()) + ->getForm(); + ``` + + After: + + ```php + use Symfony\Component\Validator\Constraints\Valid; + + $form = $this->createFormBuilder($article) + ->add('author', new AuthorType(), array( + 'constraints' => new Valid(), + )) + ->getForm(); + ``` + + Alternatively, you can set the `Valid` constraint in the model itself: + + ```php + use Symfony\Component\Validator\Constraints as Assert; + + class Article + { + /** + * @Assert\Valid + */ + private $author; + } + ``` + + * Type names were deprecated and will be removed in Symfony 3.0. Instead of + referencing types by name, you should reference them by their + fully-qualified class name (FQCN) instead. With PHP 5.5 or later, you can + use the "class" constant for that: + + Before: + + ```php + $form = $this->createFormBuilder() + ->add('name', 'text') + ->add('age', 'integer') + ->getForm(); + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\IntegerType; + use Symfony\Component\Form\Extension\Core\Type\TextType; + + $form = $this->createFormBuilder() + ->add('name', TextType::class) + ->add('age', IntegerType::class) + ->getForm(); + ``` + + As a further consequence, the method `FormTypeInterface::getName()` was + deprecated and will be removed in Symfony 3.0. You should remove this method + from your form types. + + If you want to customize the block prefix of a type in Twig, you should now + implement `FormTypeInterface::getBlockPrefix()` instead: + + Before: + + ```php + class UserProfileType extends AbstractType + { + public function getName() + { + return 'profile'; + } + } + ``` + + After: + + ```php + class UserProfileType extends AbstractType + { + public function getBlockPrefix() + { + return 'profile'; + } + } + ``` + + If you don't customize `getBlockPrefix()`, it defaults to the class name + without "Type" suffix in underscore notation (here: "user_profile"). + + If you want to create types that are compatible with Symfony 2.3 up to 2.8 + and don't trigger deprecation errors, implement *both* `getName()` and + `getBlockPrefix()`: + + ```php + class ProfileType extends AbstractType + { + public function getName() + { + return $this->getBlockPrefix(); + } + + public function getBlockPrefix() + { + return 'profile'; + } + } + ``` + + If you define your form types in the Dependency Injection configuration, you + should further remove the "alias" attribute: + + Before: + + ```xml + + + + ``` + + After: + + ```xml + + + + ``` + + Type extension should return the fully-qualified class name of the extended + type from `FormTypeExtensionInterface::getExtendedType()` now. + + Before: + + ```php + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'form'; + } + } + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + } + ``` + + If your extension has to be compatible with Symfony 2.3-2.8, use the + following statement: + + ```php + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return method_exists(AbstractType::class, 'getBlockPrefix') ? FormType::class : 'form'; + } + } + ``` + + * In Symfony 2.7 a small BC break was introduced with the new choices_as_values + option. In order to have the choice values populated to the html value attribute + you had to define the choice_value option. This is now not any more needed. + + Before: + + ```php + $form->add('status', 'choice', array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ), + 'choices_as_values' => true, + // important if you rely on your option value attribute (e.g. for JavaScript) + // this will keep the same functionality as before + 'choice_value' => function ($choice) { + return $choice; + }, + )); + ``` + + After (Symfony 2.8+): + + ```php + $form->add('status', ChoiceType::class, array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ), + 'choices_as_values' => true + )); + ``` + + * Returning type instances from `FormTypeInterface::getParent()` is deprecated + and will not be supported anymore in Symfony 3.0. Return the fully-qualified + class name of the parent type class instead. + + Before: + + ```php + class MyType + { + public function getParent() + { + return new ParentType(); + } + } + ``` + + After: + + ```php + class MyType + { + public function getParent() + { + return ParentType::class; + } + } + ``` + + * The option "options" of the CollectionType has been renamed to "entry_options". + The usage of the option "options" is deprecated and will be removed in Symfony 3.0. + + * The option "type" of the CollectionType has been renamed to "entry_type". + The usage of the option "type" is deprecated and will be removed in Symfony 3.0. + As a value for the option you should provide the fully-qualified class name (FQCN) + now as well. + + * Passing type instances to `Form::add()`, `FormBuilder::add()` and the + `FormFactory::create*()` methods is deprecated and will not be supported + anymore in Symfony 3.0. Pass the fully-qualified class name of the type + instead. + + Before: + + ```php + $form = $this->createForm(new MyType()); + ``` + + After: + + ```php + $form = $this->createForm(MyType::class); + ``` + + * Registering type extensions as a service with an alias which does not + match the type returned by `getExtendedType` is now forbidden. Fix your + implementation to define the right type. + + * The alias option of the `form.type_extension` tag is deprecated in favor of + the `extended_type`/`extended-type` option. + + Before: + ```xml + + + + ``` + + After: + ```xml + + + + ``` + + * The `TimezoneType::getTimezones()` method was deprecated and will be removed + in Symfony 3.0. You should not use this method. + + * The class `ArrayKeyChoiceList` was deprecated and will be removed in Symfony + 3.0. Use `ArrayChoiceList` instead. + +Translator +---------- + + * The `getMessages()` method of the `Symfony\Component\Translation\Translator` was deprecated and will be removed in + Symfony 3.0. You should use the `getCatalogue()` method of the `Symfony\Component\Translation\TranslatorBagInterface`. + + Before: + + ```php + $messages = $translator->getMessages(); + ``` + + After: + + ```php + $catalogue = $translator->getCatalogue($locale); + $messages = $catalogue->all(); + + while ($catalogue = $catalogue->getFallbackCatalogue()) { + $messages = array_replace_recursive($catalogue->all(), $messages); + } + ``` + +DependencyInjection +------------------- + + * The concept of scopes were deprecated, the deprecated methods are: + + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopes()` + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopeChildren()` + - `Symfony\Component\DependencyInjection\ContainerInterface::enterScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::leaveScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::addScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::hasScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::isScopeActive()` + - `Symfony\Component\DependencyInjection\Definition::setScope()` + - `Symfony\Component\DependencyInjection\Definition::getScope()` + - `Symfony\Component\DependencyInjection\Reference::isStrict()` + + Also, the `$scope` and `$strict` parameters of `Symfony\Component\DependencyInjection\ContainerInterface::set()` and `Symfony\Component\DependencyInjection\Reference` respectively were deprecated. + + * A new `shared` flag has been added to the service definition + in replacement of the `prototype` scope. + + Before: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setScope(ContainerBuilder::SCOPE_PROTOTYPE) + ; + ``` + + ```yml + services: + foo: + class: stdClass + scope: prototype + ``` + + ```xml + + + + ``` + + After: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setShared(false) + ; + ``` + + ```yml + services: + foo: + class: stdClass + shared: false + ``` + + ```xml + + + + ``` + + * `Symfony\Component\DependencyInjection\ContainerAware` has been deprecated, use + `Symfony\Component\DependencyInjection\ContainerAwareTrait` or implement + `Symfony\Component\DependencyInjection\ContainerAwareInterface` manually + +WebProfiler +----------- + + * The `profiler:import` and `profiler:export` commands have been deprecated and + will be removed in 3.0. + + * The web development toolbar has been completely redesigned. This update has + introduced some changes in the HTML markup of the toolbar items. + + Before: + + Information was wrapped with simple `` elements: + + ```twig + {% block toolbar %} + {% set icon %} + + + {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MB + + {% endset %} + {% endblock %} + ``` + + After: + + Information is now semantically divided into values and labels according to + the `class` attribute of each `` element: + + ```twig + {% block toolbar %} + {% set icon %} + + + {{ '%.1f'|format(collector.memory / 1024 / 1024) }} + + MB + {% endset %} + {% endblock %} + ``` + + Most of the blocks designed for the previous toolbar will still be displayed + correctly. However, if you want to support both the old and the new toolbar, + it's better to make use of the new `profiler_markup_version` variable passed + to the toolbar templates: + + ```twig + {% block toolbar %} + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {% set icon %} + {% if profiler_markup_version == 1 %} + + {# code for the original toolbar #} + + {% else %} + + {# code for the new toolbar (Symfony 2.8+) #} + + {% endif %} + {% endset %} + {% endblock %} + ``` + + * All the profiler storages different than `FileProfilerStorage` have been + deprecated. The deprecated classes are: + + - `Symfony\Component\HttpKernel\Profiler\BaseMemcacheProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MemcachedProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MemcacheProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MongoDbProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MysqlProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\PdoProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\RedisProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage` + + The alternative solution is to use the `FileProfilerStorage` or create your + own storage implementing the `ProfileStorageInterface`. + +FrameworkBundle +--------------- + + * The default value of the parameter `session`.`cookie_httponly` is now `true`. + It prevents scripting languages, such as JavaScript to access the cookie, + which help to reduce identity theft through XSS attacks. If your + application needs to access the session cookie, override this parameter: + + ```yaml + framework: + session: + cookie_httponly: false + ``` + + * The `validator.mapping.cache.apc` service is deprecated, and will be removed in 3.0. + Use `validator.mapping.cache.doctrine.apc` instead. + + * The ability to pass `apc` as the `framework.validation.cache` configuration key value is deprecated, + and will be removed in 3.0. Use `validator.mapping.cache.doctrine.apc` instead: + + Before: + + ```yaml + framework: + validation: + cache: apc + ``` + + After: + + ```yaml + framework: + validation: + cache: validator.mapping.cache.doctrine.apc + ``` + +Security +-------- + + * The `object` variable passed to expressions evaluated by the `ExpressionVoter` + is deprecated. Instead use the new `subject` variable. + + * The `AbstractVoter` class was deprecated. Instead, extend the `Voter` class and + move your voting logic in the `supports($attribute, $subject)` and + `voteOnAttribute($attribute, $object, TokenInterface $token)` methods. + + * The `VoterInterface::supportsClass` and `supportsAttribute` methods were + deprecated and will be removed from the interface in 3.0. + + * The `key` setting of `anonymous`, `remember_me` and `http_digest` is + deprecated, and will be removed in 3.0. Use `secret` instead. + + Before: + + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { key: "%secret%" } + remember_me: + key: "%secret%" + http_digest: + key: "%secret%" + ``` + + ```xml + + + + + + + + + + + + + ``` + + ```php + // ... + $container->loadFromExtension('security', array( + // ... + 'firewalls' => array( + // ... + 'anonymous' => array('key' => '%secret%'), + 'remember_me' => array('key' => '%secret%'), + 'http_digest' => array('key' => '%secret%'), + ), + )); + ``` + + After: + + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { secret: "%secret%" } + remember_me: + secret: "%secret%" + http_digest: + secret: "%secret%" + ``` + + ```xml + + + + + + + + + + + + + ``` + + ```php + // ... + $container->loadFromExtension('security', array( + // ... + 'firewalls' => array( + // ... + 'anonymous' => array('secret' => '%secret%'), + 'remember_me' => array('secret' => '%secret%'), + 'http_digest' => array('secret' => '%secret%'), + ), + )); + ``` + + * The `intention` option is deprecated for all the authentication listeners, + and will be removed in 3.0. Use the `csrf_token_id` option instead. + + * The `csrf_provider` option is deprecated for all the authentication listeners, + and will be removed in 3.0. Use the `csrf_token_generator` option instead. + +SecurityBundle +-------------- + + * The `intention` firewall listener setting is deprecated, and will be removed in 3.0. + Use the `csrf_token_id` option instead. + + * The `csrf_provider` firewall listener setting is deprecated, and will be removed in 3.0. + Use the `csrf_token_generator` option instead. + +Config +------ + + * The `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` method has been + deprecated and will be removed in Symfony 3.0 because it assumes that resource + implementations are able to check themselves for freshness. + + If you have custom resources that implement this method, change them to implement the + `\Symfony\Component\Config\Resource\SelfCheckingResourceInterface` sub-interface instead + of `\Symfony\Component\Config\Resource\ResourceInterface`. + + Before: + + ```php + use Symfony\Component\Config\Resource\ResourceInterface; + + class MyCustomResource implements ResourceInterface { ... } + ``` + + After: + + ```php + use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; + + class MyCustomResource implements SelfCheckingResourceInterface { ... } + ``` + + Additionally, if you have implemented cache validation strategies *using* `isFresh()` + yourself, you should have a look at the new cache validation system based on + `ResourceChecker`s. + +Yaml +---- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of `@`, `` ` ``, `|`, and `>` at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +HttpFoundation +-------------- + + * Deprecated finding deep items in `ParameterBag::get()`. This may affect you + when getting parameters from the `Request` class: + + Before: + + ```php + $request->query->get('foo[bar]', null, true); + ``` + + After: + + ```php + $request->query->get('foo')['bar']; + ``` + +Routing +------- + + * Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method. + Use the constants defined in the `UrlGeneratorInterface` instead. + + Before: + + ```php + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), true); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + ``` + +DoctrineBridge +-------------- + * Deprecated using the entity provider with a Doctrine repository implementing `UserProviderInterface`. + Make it implement `UserLoaderInterface` instead. diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 732869a9620cb..f9524e7710f16 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -51,6 +51,11 @@ UPGRADE FROM 2.x to 3.0 ### Config + * `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` has been removed. Also, + cache validation through this method (which was still supported in 2.8 for BC) does no longer + work because the `\Symfony\Component\Config\Resource\BCResourceInterfaceChecker` helper class + has been removed as well. + * The `__toString()` method of the `\Symfony\Component\Config\ConfigCache` class was removed in favor of the new `getPath()` method. @@ -122,8 +127,87 @@ UPGRADE FROM 2.x to 3.0 $table->render(); ``` +* Parameters of `renderException()` method of the + `Symfony\Component\Console\Application` are type hinted. + You must add the type hint to your implementations. + ### DependencyInjection + * The method `remove` was added to `Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface`. + + * The concept of scopes was removed, the removed methods are: + + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopes()` + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopeChildren()` + - `Symfony\Component\DependencyInjection\ContainerInterface::enterScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::leaveScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::addScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::hasScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::isScopeActive()` + - `Symfony\Component\DependencyInjection\Definition::setScope()` + - `Symfony\Component\DependencyInjection\Definition::getScope()` + - `Symfony\Component\DependencyInjection\Reference::isStrict()` + + Also, the `$scope` and `$strict` parameters of `Symfony\Component\DependencyInjection\ContainerInterface::set()` + and `Symfony\Component\DependencyInjection\Reference` respectively were removed. + + * A new `shared` flag has been added to the service definition + in replacement of the `prototype` scope. + + Before: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setScope(ContainerBuilder::SCOPE_PROTOTYPE) + ; + ``` + + ```yml + services: + foo: + class: stdClass + scope: prototype + ``` + + ```xml + + + + ``` + + After: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setShared(false) + ; + ``` + + ```yml + services: + foo: + class: stdClass + shared: false + ``` + + ```xml + + + + ``` + + * `Symfony\Component\DependencyInjection\ContainerAware` was removed, use + `Symfony\Component\DependencyInjection\ContainerAwareTrait` or implement + `Symfony\Component\DependencyInjection\ContainerAwareInterface` manually + * The methods `Definition::setFactoryClass()`, `Definition::setFactoryMethod()`, and `Definition::setFactoryService()` have been removed in favor of `Definition::setFactory()`. Services defined using @@ -133,6 +217,22 @@ UPGRADE FROM 2.x to 3.0 removed: `ContainerBuilder::synchronize()`, `Definition::isSynchronized()`, and `Definition::setSynchronized()`. +### DomCrawler + + * The interface of the `Symfony\Component\DomCrawler\Crawler` changed. It does no longer implement `\Iterator` but `\IteratorAggregate`. If you rely on methods of the `\Iterator` interface, call the `getIterator` method of the `\IteratorAggregate` interface before. No changes are required in a `\Traversable`-aware control structure, such as `foreach`. + + Before: + + ```php + $crawler->current(); + ``` + + After: + + ```php + $crawler->getIterator()->current(); + ``` + ### DoctrineBridge * The `property` option of `DoctrineType` was removed in favor of the `choice_label` option. @@ -153,18 +253,240 @@ UPGRADE FROM 2.x to 3.0 closures, but the closure is now resolved in the type instead of in the loader. + * Using the entity provider with a Doctrine repository implementing `UserProviderInterface` is not supported anymore. + You should make the repository implement `UserLoaderInterface` instead. + ### EventDispatcher + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. * The interface `Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface` extends `Symfony\Component\EventDispatcher\EventDispatcherInterface`. ### Form + * The `getBlockPrefix()` method was added to the `FormTypeInterface` in replacement of + the `getName()` method which has been removed. + + * The `configureOptions()` method was added to the `FormTypeInterface` in replacement + of the `setDefaultOptions()` method which has been removed. + + * The `getBlockPrefix()` method was added to the `ResolvedFormTypeInterface` in + replacement of the `getName()` method which has been removed. + + * The option `options` of the `CollectionType` has been removed in favor + of the `entry_options` option. + + * The `cascade_validation` option was removed. Use the `constraints` option + together with the `Valid` constraint instead. + + * Type names were removed. Instead of referencing types by name, you must + reference them by their fully-qualified class name (FQCN) instead: + + Before: + + ```php + $form = $this->createFormBuilder() + ->add('name', 'text') + ->add('age', 'integer') + ->getForm(); + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\IntegerType; + use Symfony\Component\Form\Extension\Core\Type\TextType; + + $form = $this->createFormBuilder() + ->add('name', TextType::class) + ->add('age', IntegerType::class) + ->getForm(); + ``` + + If you want to customize the block prefix of a type in Twig, you must now + implement `FormTypeInterface::getBlockPrefix()`: + + Before: + + ```php + class UserProfileType extends AbstractType + { + public function getName() + { + return 'profile'; + } + } + ``` + + After: + + ```php + class UserProfileType extends AbstractType + { + public function getBlockPrefix() + { + return 'profile'; + } + } + ``` + + If you don't customize `getBlockPrefix()`, it defaults to the class name + without "Type" suffix in underscore notation (here: "user_profile"). + + Type extension must return the fully-qualified class name of the extended + type from `FormTypeExtensionInterface::getExtendedType()` now. + + Before: + + ```php + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'form'; + } + } + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + } + ``` + + * The `FormTypeInterface::getName()` method was removed. + + * Returning type instances from `FormTypeInterface::getParent()` is not + supported anymore. Return the fully-qualified class name of the parent + type class instead. + + Before: + + ```php + class MyType + { + public function getParent() + { + return new ParentType(); + } + } + ``` + + After: + + ```php + class MyType + { + public function getParent() + { + return ParentType::class; + } + } + ``` + + * The option `type` of the `CollectionType` has been removed in favor of + the `entry_type` option. The value for the `entry_type` option must be + the fully-qualified class name (FQCN). + + * Passing type instances to `Form::add()`, `FormBuilder::add()` and the + `FormFactory::create*()` methods is not supported anymore. Pass the + fully-qualified class name of the type instead. + + Before: + + ```php + $form = $this->createForm(new MyType()); + ``` + + After: + + ```php + $form = $this->createForm(MyType::class); + ``` + + * Passing custom data to forms now needs to be done + through the options resolver. + + In the controller: + + Before: + ```php + $form = $this->createForm(new MyType($variable), $entity, array( + 'action' => $this->generateUrl('action_route'), + 'method' => 'PUT', + )); + ``` + After: + ```php + $form = $this->createForm(MyType::class, $entity, array( + 'action' => $this->generateUrl('action_route'), + 'method' => 'PUT', + 'custom_value' => $variable, + )); + ``` + In the form type: + + Before: + ```php + class MyType extends AbstractType + { + private $value; + + public function __construct($variableValue) + { + $this->value = $value; + } + // ... + } + ``` + + After: + ```php + public function buildForm(FormBuilderInterface $builder, array $options) + { + $value = $options['custom_value']; + // ... + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'custom_value' => null, + )); + } + ``` + + * The alias option of the `form.type_extension` tag was removed in favor of + the `extended_type`/`extended-type` option. + + Before: + ```xml + + + + ``` + + After: + ```xml + + + + ``` + * The `max_length` option was removed. Use the `attr` option instead by setting it to an `array` with a `maxlength` key. * The `ChoiceToBooleanArrayTransformer`, `ChoicesToBooleanArrayTransformer`, - `FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed. + `FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed. * The `choice_list` option of `ChoiceType` was removed. @@ -173,7 +495,9 @@ UPGRADE FROM 2.x to 3.0 Before: ```php - $builder->add('length', 'number', array( + use Symfony\Component\Form\Extension\Core\Type\NumberType; + + $builder->add('length', NumberType::class, array( 'precision' => 3, )); ``` @@ -181,11 +505,35 @@ UPGRADE FROM 2.x to 3.0 After: ```php - $builder->add('length', 'number', array( + use Symfony\Component\Form\Extension\Core\Type\NumberType; + + $builder->add('length', NumberType::class, array( 'scale' => 3, )); ``` + * The option "`virtual`" was renamed to "`inherit_data`". + + Before: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + $builder->add('address', FormType::class, array( + 'virtual' => true, + )); + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + $builder->add('address', FormType::class, array( + 'inherit_data' => true, + )); + ``` + * The method `AbstractType::setDefaultOptions(OptionsResolverInterface $resolver)` and `AbstractTypeExtension::setDefaultOptions(OptionsResolverInterface $resolver)` have been renamed. You should use `AbstractType::configureOptions(OptionsResolver $resolver)` and @@ -267,24 +615,6 @@ UPGRADE FROM 2.x to 3.0 }); ``` - * The option "`virtual`" was renamed to "`inherit_data`". - - Before: - - ```php - $builder->add('address', 'form', array( - 'virtual' => true, - )); - ``` - - After: - - ```php - $builder->add('address', 'form', array( - 'inherit_data' => true, - )); - ``` - * The class `VirtualFormAwareIterator` was renamed to `InheritDataAwareIterator`. Before: @@ -327,6 +657,12 @@ UPGRADE FROM 2.x to 3.0 } ``` + * The option "options" of the CollectionType has been renamed to "entry_options". + + * The option "type" of the CollectionType has been renamed to "entry_type". + As a value for the option you must provide the fully-qualified class name (FQCN) + now as well. + * The `FormIntegrationTestCase` and `FormPerformanceTestCase` classes were moved form the `Symfony\Component\Form\Tests` namespace to the `Symfony\Component\Form\Test` namespace. * The constants `ROUND_HALFEVEN`, `ROUND_HALFUP` and `ROUND_HALFDOWN` in class @@ -374,6 +710,12 @@ UPGRADE FROM 2.x to 3.0 * The `Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList` class has been removed in favor of `Symfony\Component\Form\ChoiceList\ArrayChoiceList`. + * The `TimezoneType::getTimezones()` method was removed. You should not use + this method. + + * The `Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList` class has been removed in + favor of `Symfony\Component\Form\ChoiceList\ArrayChoiceList`. + ### FrameworkBundle * The `config:debug`, `container:debug`, `router:debug`, `translation:debug` @@ -381,6 +723,11 @@ UPGRADE FROM 2.x to 3.0 be removed in Symfony 3.0. Use the `debug:config`, `debug:container`, `debug:router`, `debug:translation` and `lint:yaml` commands instead. + * The base `Controller`class is now abstract. + + * The visibility of all methods of the base `Controller` class has been changed from + `public` to `protected`. + * The `getRequest` method of the base `Controller` class has been deprecated since Symfony 2.4 and must be therefore removed in 3.0. The only reliable way to get the `Request` object is to inject it in the action method. @@ -416,6 +763,42 @@ UPGRADE FROM 2.x to 3.0 } ``` + * In Symfony 2.7 a small BC break was introduced with the new choices_as_values + option. In order to have the choice values populated to the html value attribute + you had to define the choice_value option. This is now not any more needed. + + Before: + + ```php + $form->add('status', 'choice', array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ), + // choices_as_values defaults to true in Symfony 3.0 + // and setting it to anything else is deprecated as of 3.0 + 'choices_as_values' => true, + // important if you rely on your option value attribute (e.g. for JavaScript) + // this will keep the same functionality as before + 'choice_value' => function ($choice) { + return $choice; + }, + )); + ``` + + After: + + ```php + $form->add('status', ChoiceType::class, array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ) + )); + ``` + * The `request` service was removed. You must inject the `request_stack` service instead. @@ -471,6 +854,8 @@ UPGRADE FROM 2.x to 3.0 * The `RouterApacheDumperCommand` was removed. + * The `createEsi` method of `Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache` was removed. Use `createSurrogate` instead. + * The `templating.helper.router` service was moved to `templating_php.xml`. You have to ensure that the PHP templating engine is enabled to be able to use it: @@ -486,6 +871,27 @@ UPGRADE FROM 2.x to 3.0 interface. The `security.csrf.token_manager` should be used instead. + * The `validator.mapping.cache.apc` service has been removed in favor of the `validator.mapping.cache.doctrine.apc` one. + + * The ability to pass `apc` as the `framework.validation.cache` configuration key value has been removed. + Use `validator.mapping.cache.doctrine.apc` instead: + + Before: + + ```yaml + framework: + validation: + cache: apc + ``` + + After: + + ```yaml + framework: + validation: + cache: validator.mapping.cache.doctrine.apc + ``` + ### HttpKernel * The `Symfony\Component\HttpKernel\Log\LoggerInterface` has been removed in @@ -617,10 +1023,220 @@ UPGRADE FROM 2.x to 3.0 * The `getMatcherDumperInstance()` and `getGeneratorDumperInstance()` methods in the `Symfony\Component\Routing\Router` have been changed from `public` to `protected`. + * Use the constants defined in the UrlGeneratorInterface for the $referenceType argument of the UrlGeneratorInterface::generate method. + + Before: + + ```php + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), true); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + ``` + ### Security + * The `vote()` method from the `VoterInterface` was changed to now accept arbitrary + types and not only objects. You can rely on the new abstract `Voter` class introduced + in 2.8 to ease integrating your own voters. + + * The `AbstractVoter` class was removed in favor of the new `Voter` class. + * The `Resources/` directory was moved to `Core/Resources/` + * The `key` settings of `anonymous`, `remember_me` and `http_digest` are + renamed to `secret`. + + Before: + + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { key: "%secret%" } + remember_me: + key: "%secret%" + http_digest: + key: "%secret%" + ``` + + ```xml + + + + + + + + + + + + + ``` + + ```php + // ... + $container->loadFromExtension('security', array( + // ... + 'firewalls' => array( + // ... + 'anonymous' => array('key' => '%secret%'), + 'remember_me' => array('key' => '%secret%'), + 'http_digest' => array('key' => '%secret%'), + ), + )); + ``` + + After: + + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { secret: "%secret%" } + remember_me: + secret: "%secret%" + http_digest: + secret: "%secret%" + ``` + + ```xml + + + + + + + + + + + + + ``` + + ```php + // ... + $container->loadFromExtension('security', array( + // ... + 'firewalls' => array( + // ... + 'anonymous' => array('secret' => '%secret%'), + 'remember_me' => array('secret' => '%secret%'), + 'http_digest' => array('secret' => '%secret%'), + ), + )); + ``` + + * The `AbstractVoter` class was removed. Instead, extend the new `Voter` class, + introduced in 2.8, and move your voting logic to the to the `supports($attribute, $subject)` + and `voteOnAttribute($attribute, $object, TokenInterface $token)` methods. + + * The `vote()` method from the `VoterInterface` was changed to now accept arbitrary + types, and not only objects. + + * The `supportsClass` and `supportsAttribute` methods were + removed from the `VoterInterface` interface. + + Before: + + ```php + class MyVoter extends AbstractVoter + { + protected function getSupportedAttributes() + { + return array('CREATE', 'EDIT'); + } + + protected function getSupportedClasses() + { + return array('AppBundle\Entity\Post'); + } + + // ... + } + ``` + + After: + + ```php + use Symfony\Component\Security\Core\Authorization\Voter\Voter; + + class MyVoter extends Voter + { + protected function supports($attribute, $object) + { + return $object instanceof Post && in_array($attribute, array('CREATE', 'EDIT')); + } + + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + // Return true or false + } + } + ``` + + * The `AbstractVoter::isGranted()` method has been replaced by `Voter::voteOnAttribute()`. + + Before: + + ```php + class MyVoter extends AbstractVoter + { + protected function isGranted($attribute, $object, $user = null) + { + return 'EDIT' === $attribute && $user === $object->getAuthor(); + } + + // ... + } + ``` + + After: + + ```php + class MyVoter extends Voter + { + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + return 'EDIT' === $attribute && $token->getUser() === $object->getAuthor(); + } + + // ... + } + ``` + + * The `supportsAttribute()` and `supportsClass()` methods of the `AuthenticatedVoter`, `ExpressionVoter`, + and `RoleVoter` classes have been removed. + + * The `intention` option was renamed to `csrf_token_id` for all the authentication listeners. + + * The `csrf_provider` option was renamed to `csrf_token_generator` for all the authentication listeners. + +### SecurityBundle + + * The `intention` firewall listener setting was renamed to `csrf_token_id`. + + * The `csrf_provider` firewall listener setting was renamed to `csrf_token_generator`. + ### Serializer * The `setCamelizedAttributes()` method of the @@ -636,6 +1252,72 @@ UPGRADE FROM 2.x to 3.0 * The `Translator::setFallbackLocale()` method has been removed in favor of `Translator::setFallbackLocales()`. + * The visibility of the `locale` property has been changed from protected to private. Rely on `getLocale` and `setLocale` + instead. + + Before: + + ```php + class CustomTranslator extends Translator + { + public function fooMethod() + { + // get locale + $locale = $this->locale; + + // update locale + $this->locale = $locale; + } + } + ``` + + After: + + ```php + class CustomTranslator extends Translator + { + public function fooMethod() + { + // get locale + $locale = $this->getLocale(); + + // update locale + $this->setLocale($locale); + } + } + ``` + + * The method `FileDumper::format()` was removed. You should use + `FileDumper::formatCatalogue()` instead. + + Before: + + ```php + class CustomDumper extends FileDumper + { + protected function format(MessageCatalogue $messages, $domain) + { + ... + } + } + ``` + + After: + + ```php + class CustomDumper extends FileDumper + { + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + { + ... + } + } + ``` + + * The `getMessages()` method of the `Symfony\Component\Translation\Translator` + class was removed. You should use the `getCatalogue()` method of the + `Symfony\Component\Translation\TranslatorBagInterface`. + ### Twig Bridge * The `twig:lint` command has been deprecated since Symfony 2.7 and will be @@ -1140,6 +1822,24 @@ UPGRADE FROM 2.x to 3.0 ### Yaml + * Using a colon in an unquoted mapping value leads to a `ParseException`. + * Starting an unquoted string with `@`, `` ` ``, `|`, or `>` leads to a `ParseException`. + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) leads to a `ParseException`. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + + * The ability to pass file names to `Yaml::parse()` has been removed. Before: @@ -1154,6 +1854,22 @@ UPGRADE FROM 2.x to 3.0 Yaml::parse(file_get_contents($fileName)); ``` +### WebProfiler + + * The `profiler:import` and `profiler:export` commands have been removed. + + * All the profiler storages different than `FileProfilerStorage` have been + removed. The removed classes are: + + - `Symfony\Component\HttpKernel\Profiler\BaseMemcacheProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MemcachedProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MemcacheProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MongoDbProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MysqlProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\PdoProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\RedisProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage` + ### Process * `Process::setStdin()` and `Process::getStdin()` have been removed. Use @@ -1166,3 +1882,34 @@ UPGRADE FROM 2.x to 3.0 * `Symfony\Bridge\Monolog\Logger::crit()` was removed. Use `critical()` which is PSR-3 compatible. * `Symfony\Bridge\Monolog\Logger::err()` was removed. Use `error()` which is PSR-3 compatible. * `Symfony\Bridge\Monolog\Logger::warn()` was removed. Use `warning()` which is PSR-3 compatible. + +### Swiftmailer Bridge + + * `Symfony\Bridge\Swiftmailer\DataCollector\MessageDataCollector` was removed. Use the `Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector` class instead. + +### HttpFoundation + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + + * `Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface` no longer implements the `IteratorAggregate` interface. Use the `all()` method instead of iterating over the flash bag. + + * Removed the feature that allowed finding deep items in `ParameterBag::get()`. + This may affect you when getting parameters from the `Request` class: + + Before: + + ```php + $request->query->get('foo[bar]', null, true); + ``` + + After: + + ```php + $request->query->get('foo')['bar']; + ``` +### Monolog Bridge + + * `Symfony\Bridge\Monolog\Logger::emerg()` was removed. Use `emergency()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::crit()` was removed. Use `critical()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::err()` was removed. Use `error()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::warn()` was removed. Use `warning()` which is PSR-3 compatible. diff --git a/appveyor.yml b/appveyor.yml index 5cbbb67386626..e23696c240fc2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -45,6 +45,7 @@ install: - echo extension=php_mbstring.dll >> php.ini-max - echo extension=php_fileinfo.dll >> php.ini-max - echo extension=php_pdo_sqlite.dll >> php.ini-max + - echo extension=php_ldap.dll >> php.ini-max - echo extension=php_curl.dll >> php.ini-max - echo curl.cainfo=c:\php\cacert.pem >> php.ini-max - copy /Y php.ini-min php.ini diff --git a/composer.json b/composer.json index 350ea8cce2185..f09d3dfedbf32 100644 --- a/composer.json +++ b/composer.json @@ -19,12 +19,18 @@ "php": ">=5.3.9", "ext-xml": "*", "doctrine/common": "~2.4", - "paragonie/random_compat": "~1.0", + "twig/twig": "~1.34|~2.4", + "psr/log": "~1.0", + "symfony/security-acl": "~2.7|~3.0.0", "symfony/polyfill-apcu": "~1.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.1", - "twig/twig": "~1.34|~2.4", - "psr/log": "~1.0" + "symfony/polyfill-intl-icu": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php54": "~1.0", + "symfony/polyfill-php55": "~1.0", + "symfony/polyfill-php56": "~1.0", + "symfony/polyfill-php70": "~1.0", + "symfony/polyfill-util": "~1.0" }, "replace": { "symfony/asset": "self.version", @@ -47,17 +53,19 @@ "symfony/http-foundation": "self.version", "symfony/http-kernel": "self.version", "symfony/intl": "self.version", + "symfony/ldap": "self.version", "symfony/locale": "self.version", "symfony/monolog-bridge": "self.version", "symfony/options-resolver": "self.version", "symfony/process": "self.version", "symfony/property-access": "self.version", + "symfony/property-info": "self.version", "symfony/proxy-manager-bridge": "self.version", "symfony/routing": "self.version", "symfony/security": "self.version", - "symfony/security-acl": "self.version", "symfony/security-core": "self.version", "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", "symfony/security-http": "self.version", "symfony/security-bundle": "self.version", "symfony/serializer": "self.version", @@ -79,13 +87,14 @@ "doctrine/orm": "~2.4,>=2.4.5", "doctrine/doctrine-bundle": "~1.2", "monolog/monolog": "~1.11", - "ircmaxell/password-compat": "~1.0", "ocramius/proxy-manager": "~0.4|~1.0|~2.0", "symfony/phpunit-bridge": "~3.4|~4.0", "egulias/email-validator": "~1.2,>=1.2.1", + "phpdocumentor/reflection": "^1.0.7", "sensio/framework-extra-bundle": "^3.0.2" }, "conflict": { + "phpdocumentor/reflection": "<1.0.7", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" }, "autoload": { @@ -99,10 +108,8 @@ "Symfony\\Component\\": "src/Symfony/Component/" }, "classmap": [ - "src/Symfony/Component/HttpFoundation/Resources/stubs", "src/Symfony/Component/Intl/Resources/stubs" ], - "files": [ "src/Symfony/Component/Intl/Resources/stubs/functions.php" ], "exclude-from-classmap": [ "**/Tests/" ] @@ -113,7 +120,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/phpunit b/phpunit index c0ffe8ddef9e9..f4b80ed064121 100755 --- a/phpunit +++ b/phpunit @@ -8,7 +8,7 @@ if (!file_exists(__DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit')) { exit(1); } if (\PHP_VERSION_ID >= 70000 && !getenv('SYMFONY_PHPUNIT_VERSION')) { - putenv('SYMFONY_PHPUNIT_VERSION=6.0'); + putenv('SYMFONY_PHPUNIT_VERSION=6.5'); } putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit'); require __DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit'; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 13156a72ce272..ada17c766aec4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -54,7 +54,12 @@ - Symfony\Component\HttpFoundation + + + Symfony\Component\Console + Symfony\Component\HttpFoundation + + diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 4d8c44701dd3a..71a2707bab709 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +2.8.0 +----- + + * deprecated using the entity provider with a Doctrine repository implementing UserProviderInterface + * added UserLoaderInterface for loading users through Doctrine. + 2.7.0 ----- diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 26079cd09f2b2..645e2fda4cc67 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -39,7 +39,7 @@ public function __construct(ManagerRegistry $registry) public function guessType($class, $property) { if (!$ret = $this->getMetadata($class)) { - return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', array(), Guess::LOW_CONFIDENCE); } list($metadata, $name) = $ret; @@ -48,35 +48,35 @@ public function guessType($class, $property) $multiple = $metadata->isCollectionValuedAssociation($property); $mapping = $metadata->getAssociationMapping($property); - return new TypeGuess('entity', array('em' => $name, 'class' => $mapping['targetEntity'], 'multiple' => $multiple), Guess::HIGH_CONFIDENCE); + return new TypeGuess('Symfony\Bridge\Doctrine\Form\Type\EntityType', array('em' => $name, 'class' => $mapping['targetEntity'], 'multiple' => $multiple), Guess::HIGH_CONFIDENCE); } switch ($metadata->getTypeOfField($property)) { case Type::TARRAY: - return new TypeGuess('collection', array(), Guess::MEDIUM_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CollectionType', array(), Guess::MEDIUM_CONFIDENCE); case Type::BOOLEAN: - return new TypeGuess('checkbox', array(), Guess::HIGH_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CheckboxType', array(), Guess::HIGH_CONFIDENCE); case Type::DATETIME: case Type::DATETIMETZ: case 'vardatetime': - return new TypeGuess('datetime', array(), Guess::HIGH_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateTimeType', array(), Guess::HIGH_CONFIDENCE); case Type::DATE: - return new TypeGuess('date', array(), Guess::HIGH_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', array(), Guess::HIGH_CONFIDENCE); case Type::TIME: - return new TypeGuess('time', array(), Guess::HIGH_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', array(), Guess::HIGH_CONFIDENCE); case Type::DECIMAL: case Type::FLOAT: - return new TypeGuess('number', array(), Guess::MEDIUM_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', array(), Guess::MEDIUM_CONFIDENCE); case Type::INTEGER: case Type::BIGINT: case Type::SMALLINT: - return new TypeGuess('integer', array(), Guess::MEDIUM_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\IntegerType', array(), Guess::MEDIUM_CONFIDENCE); case Type::STRING: - return new TypeGuess('text', array(), Guess::MEDIUM_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', array(), Guess::MEDIUM_CONFIDENCE); case Type::TEXT: - return new TypeGuess('textarea', array(), Guess::MEDIUM_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextareaType', array(), Guess::MEDIUM_CONFIDENCE); default: - return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); + return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TextType', array(), Guess::LOW_CONFIDENCE); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 357119f86d8cd..2acf7176f1eac 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -337,6 +337,6 @@ abstract public function getLoader(ObjectManager $manager, $queryBuilder, $class public function getParent() { - return 'choice'; + return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'; } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index aeea2425206c1..7ec1ea3d43341 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -31,7 +31,7 @@ public function configureOptions(OptionsResolver $resolver) if (is_callable($queryBuilder)) { $queryBuilder = call_user_func($queryBuilder, $options['em']->getRepository($options['class'])); - if (!$queryBuilder instanceof QueryBuilder) { + if (null !== $queryBuilder && !$queryBuilder instanceof QueryBuilder) { throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); } } @@ -57,7 +57,18 @@ public function getLoader(ObjectManager $manager, $queryBuilder, $class) return new ORMQueryBuilderLoader($queryBuilder, $manager, $class); } + /** + * {@inheritdoc} + */ public function getName() + { + return $this->getBlockPrefix(); + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() { return 'entity'; } diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index cc07076f9e2a5..42574edcade56 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -87,16 +87,9 @@ private function normalizeParams(array $params) } // detect if the too long string must be shorten - if (function_exists('mb_strlen')) { - if (self::MAX_STRING_LENGTH < mb_strlen($params[$index], 'UTF-8')) { - $params[$index] = mb_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]'; - continue; - } - } else { - if (self::MAX_STRING_LENGTH < strlen($params[$index])) { - $params[$index] = substr($params[$index], 0, self::MAX_STRING_LENGTH - 6).' [...]'; - continue; - } + if (self::MAX_STRING_LENGTH < mb_strlen($params[$index], 'UTF-8')) { + $params[$index] = mb_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]'; + continue; } } diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php new file mode 100644 index 0000000000000..4bf684bf3aec6 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\PropertyInfo; + +use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory; +use Doctrine\Common\Persistence\Mapping\MappingException; +use Doctrine\DBAL\Types\Type as DBALType; +use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\MappingException as OrmMappingException; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * Extracts data using Doctrine ORM and ODM metadata. + * + * @author Kévin Dunglas + */ +class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface +{ + private $classMetadataFactory; + + public function __construct(ClassMetadataFactory $classMetadataFactory) + { + $this->classMetadataFactory = $classMetadataFactory; + } + + /** + * {@inheritdoc} + */ + public function getProperties($class, array $context = array()) + { + try { + $metadata = $this->classMetadataFactory->getMetadataFor($class); + } catch (MappingException $exception) { + return; + } catch (OrmMappingException $exception) { + return; + } + + $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); + + if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && $metadata->embeddedClasses) { + $properties = array_filter($properties, function ($property) { + return false === strpos($property, '.'); + }); + + $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); + } + + return $properties; + } + + /** + * {@inheritdoc} + */ + public function getTypes($class, $property, array $context = array()) + { + try { + $metadata = $this->classMetadataFactory->getMetadataFor($class); + } catch (MappingException $exception) { + return; + } catch (OrmMappingException $exception) { + return; + } + + if ($metadata->hasAssociation($property)) { + $class = $metadata->getAssociationTargetClass($property); + + if ($metadata->isSingleValuedAssociation($property)) { + if ($metadata instanceof ClassMetadataInfo) { + $associationMapping = $metadata->getAssociationMapping($property); + + $nullable = $this->isAssociationNullable($associationMapping); + } else { + $nullable = false; + } + + return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class)); + } + + $collectionKeyType = Type::BUILTIN_TYPE_INT; + + if ($metadata instanceof ClassMetadataInfo) { + $associationMapping = $metadata->getAssociationMapping($property); + + if (isset($associationMapping['indexBy'])) { + $indexProperty = $associationMapping['indexBy']; + /** @var ClassMetadataInfo $subMetadata */ + $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); + $typeOfField = $subMetadata->getTypeOfField($indexProperty); + + if (null === $typeOfField) { + $associationMapping = $subMetadata->getAssociationMapping($indexProperty); + + /** @var ClassMetadataInfo $subMetadata */ + $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($indexProperty); + $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); + $typeOfField = $subMetadata->getTypeOfField($indexProperty); + } + + $collectionKeyType = $this->getPhpType($typeOfField); + } + } + + return array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type($collectionKeyType), + new Type(Type::BUILTIN_TYPE_OBJECT, false, $class) + )); + } + + if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && isset($metadata->embeddedClasses[$property])) { + return array(new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class'])); + } + + if ($metadata->hasField($property)) { + $typeOfField = $metadata->getTypeOfField($property); + $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); + + switch ($typeOfField) { + case DBALType::DATE: + case DBALType::DATETIME: + case DBALType::DATETIMETZ: + case 'vardatetime': + case DBALType::TIME: + return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')); + + case DBALType::TARRAY: + return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)); + + case DBALType::SIMPLE_ARRAY: + return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))); + + case DBALType::JSON_ARRAY: + return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)); + + default: + $builtinType = $this->getPhpType($typeOfField); + + return $builtinType ? array(new Type($builtinType, $nullable)) : null; + } + } + } + + /** + * Determines whether an association is nullable. + * + * @param array $associationMapping + * + * @return bool + * + * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 + */ + private function isAssociationNullable(array $associationMapping) + { + if (isset($associationMapping['id']) && $associationMapping['id']) { + return false; + } + + if (!isset($associationMapping['joinColumns'])) { + return true; + } + + $joinColumns = $associationMapping['joinColumns']; + foreach ($joinColumns as $joinColumn) { + if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) { + return false; + } + } + + return true; + } + + /** + * Gets the corresponding built-in PHP type. + * + * @param string $doctrineType + * + * @return string|null + */ + private function getPhpType($doctrineType) + { + switch ($doctrineType) { + case DBALType::SMALLINT: + case DBALType::INTEGER: + return Type::BUILTIN_TYPE_INT; + + case DBALType::FLOAT: + return Type::BUILTIN_TYPE_FLOAT; + + case DBALType::BIGINT: + case DBALType::STRING: + case DBALType::TEXT: + case DBALType::GUID: + case DBALType::DECIMAL: + return Type::BUILTIN_TYPE_STRING; + + case DBALType::BOOLEAN: + return Type::BUILTIN_TYPE_BOOL; + + case DBALType::BLOB: + case 'binary': + return Type::BUILTIN_TYPE_RESOURCE; + + case DBALType::OBJECT: + return Type::BUILTIN_TYPE_OBJECT; + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index 058eb7167e961..7356ff2998cfa 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -50,8 +50,12 @@ public function loadUserByUsername($username) if (null !== $this->property) { $user = $repository->findOneBy(array($this->property => $username)); } else { - if (!$repository instanceof UserProviderInterface) { - throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Component\Security\Core\User\UserProviderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, get_class($repository))); + if (!$repository instanceof UserLoaderInterface) { + if (!$repository instanceof UserProviderInterface) { + throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, get_class($repository))); + } + + @trigger_error('Implementing Symfony\Component\Security\Core\User\UserProviderInterface in a Doctrine repository when using the entity provider is deprecated since Symfony 2.8 and will not be supported in 3.0. Make the repository implement Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface instead.', E_USER_DEPRECATED); } $user = $repository->loadUserByUsername($username); diff --git a/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php b/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php new file mode 100644 index 0000000000000..452939fa7934a --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Security\User; + +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Represents a class that loads UserInterface objects from Doctrine source for the authentication system. + * + * This interface is meant to facilitate the loading of a User from Doctrine source using a custom method. + * If you want to implement your own logic of retrieving the user from Doctrine your repository should implement this + * interface. + * + * @see UserInterface + * + * @author Michal Trojanowski + */ +interface UserLoaderInterface +{ + /** + * Loads the user for the given username. + * + * This method must return null if the user is not found. + * + * @param string $username The username + * + * @return UserInterface|null + */ + public function loadUserByUsername($username); +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php index 5980d9c734c54..8bf2fb804f939 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php index 7324f721ec340..02e117d7baec4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php @@ -15,6 +15,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * Test choices generated from an entity with a primary foreign key. * diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php index 74af66db360e2..85ea10a451c22 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php index 56b4c21319826..1bd5ac8de4695 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php index 4f3d54a30f15f..b2d5b4a888b37 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php @@ -16,6 +16,10 @@ use Doctrine\ORM\Tools\SchemaTool; use Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php index a2ee7cdc8a64f..21fa4c56a0b57 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php index 60e3797bac865..73344be670eba 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php index f655784004fbb..f64e0cec3e538 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php index 629b399ac36a7..d0b04b8013656 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php index 114eee661efe5..305f365075ebe 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php index 422295feb1e04..75e36f90cf4e3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php index 20267bd020775..ee3afeb89909f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php index 45ab799585cb6..1b637949bbd09 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php index 0668c09bdb63f..0197f19b3b1fa 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php index c093782ff0ec7..4e75cf7491cca 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php index 39363bae26a25..3e9847f0e2eff 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php index 23329e80df3c2..51adaabba80db 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index f72d7d09d0f95..ac96ea21c5312 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -90,7 +90,7 @@ public function testCollapsedEntityField() $this->setMaxRunningTime(1); for ($i = 0; $i < 40; ++$i) { - $form = $this->factory->create('entity', null, array( + $form = $this->factory->create('Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'class' => self::ENTITY_CLASS, )); @@ -108,7 +108,7 @@ public function testCollapsedEntityFieldWithChoices() $this->setMaxRunningTime(1); for ($i = 0; $i < 40; ++$i) { - $form = $this->factory->create('entity', null, array( + $form = $this->factory->create('Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'class' => self::ENTITY_CLASS, 'choices' => $choices, )); @@ -127,7 +127,7 @@ public function testCollapsedEntityFieldWithPreferredChoices() $this->setMaxRunningTime(1); for ($i = 0; $i < 40; ++$i) { - $form = $this->factory->create('entity', null, array( + $form = $this->factory->create('Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'class' => self::ENTITY_CLASS, 'preferred_choices' => $choices, )); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index c1dfa2ad36a85..a7ed239b43a9b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -36,7 +36,7 @@ class EntityTypeTest extends BaseTypeTest { - const TESTED_TYPE = 'entity'; + const TESTED_TYPE = 'Symfony\Bridge\Doctrine\Form\Type\EntityType'; const ITEM_GROUP_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity'; const SINGLE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'; @@ -113,6 +113,19 @@ protected function persist(array $entities) // be managed! } + /** + * @group legacy + */ + public function testLegacyName() + { + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )); + + $this->assertSame('entity', $field->getConfig()->getType()->getName()); + } + /** * @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException */ @@ -213,6 +226,24 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() $field->submit('2'); } + public function testConfigureQueryBuilderWithClosureReturningNullUseDefault() + { + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'query_builder' => function () { + return; + }, + )); + + $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); + } + public function testSetDataSingleNull() { $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( @@ -1142,13 +1173,13 @@ public function testLoaderCaching() 'property3' => 2, )); - $choiceList1 = $form->get('property1')->getConfig()->getOption('choice_list'); - $choiceList2 = $form->get('property2')->getConfig()->getOption('choice_list'); - $choiceList3 = $form->get('property3')->getConfig()->getOption('choice_list'); + $choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader'); + $choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader'); + $choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader'); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $choiceList1); - $this->assertSame($choiceList1, $choiceList2); - $this->assertSame($choiceList1, $choiceList3); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); + $this->assertSame($choiceLoader1, $choiceLoader2); + $this->assertSame($choiceLoader1, $choiceLoader3); } public function testLoaderCachingWithParameters() @@ -1202,15 +1233,18 @@ public function testLoaderCachingWithParameters() 'property3' => 2, )); - $choiceList1 = $form->get('property1')->getConfig()->getOption('choice_list'); - $choiceList2 = $form->get('property2')->getConfig()->getOption('choice_list'); - $choiceList3 = $form->get('property3')->getConfig()->getOption('choice_list'); + $choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader'); + $choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader'); + $choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader'); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $choiceList1); - $this->assertSame($choiceList1, $choiceList2); - $this->assertSame($choiceList1, $choiceList3); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); + $this->assertSame($choiceLoader1, $choiceLoader2); + $this->assertSame($choiceLoader1, $choiceLoader3); } + /** + * @group legacy + */ public function testCacheChoiceLists() { $entity1 = new SingleIntIdEntity(1, 'Foo'); @@ -1231,8 +1265,8 @@ public function testCacheChoiceLists() 'choice_label' => 'name', )); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $field1->getConfig()->getOption('choice_list')); - $this->assertSame($field1->getConfig()->getOption('choice_list'), $field2->getConfig()->getOption('choice_list')); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $field1->getConfig()->getOption('choice_loader')); + $this->assertSame($field1->getConfig()->getOption('choice_loader'), $field2->getConfig()->getOption('choice_loader')); } /** diff --git a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php index 5a627dc23155b..1e3e6ca6ec809 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php @@ -133,9 +133,6 @@ public function testLogLongString() )); } - /** - * @requires extension mbstring - */ public function testLogUTF8LongString() { $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php new file mode 100644 index 0000000000000..8bc25718ecd6f --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo; + +use Doctrine\DBAL\Types\Type as DBALType; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Tools\Setup; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; +use Symfony\Component\PropertyInfo\Type; + +/** + * @author Kévin Dunglas + */ +class DoctrineExtractorTest extends TestCase +{ + /** + * @var DoctrineExtractor + */ + private $extractor; + + protected function setUp() + { + $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'), true); + $entityManager = EntityManager::create(array('driver' => 'pdo_sqlite'), $config); + + if (!DBALType::hasType('foo')) { + DBALType::addType('foo', 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineFooType'); + $entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('custom_foo', 'foo'); + } + + $this->extractor = new DoctrineExtractor($entityManager->getMetadataFactory()); + } + + public function testGetProperties() + { + $this->assertEquals( + array( + 'id', + 'guid', + 'time', + 'json', + 'simpleArray', + 'float', + 'decimal', + 'bool', + 'binary', + 'customFoo', + 'bigint', + 'foo', + 'bar', + 'indexedBar', + 'indexedFoo', + ), + $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') + ); + } + + public function testGetPropertiesWithEmbedded() + { + if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { + $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); + } + + $this->assertEquals( + array( + 'id', + 'embedded', + ), + $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded') + ); + } + + /** + * @dataProvider typesProvider + */ + public function testExtract($property, array $type = null) + { + $this->assertEquals($type, $this->extractor->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array())); + } + + public function testExtractWithEmbedded() + { + if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { + $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); + } + + $expectedTypes = array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable' + )); + + $actualTypes = $this->extractor->getTypes( + 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded', + 'embedded', + array() + ); + + $this->assertEquals($expectedTypes, $actualTypes); + } + + public function typesProvider() + { + return array( + array('id', array(new Type(Type::BUILTIN_TYPE_INT))), + array('guid', array(new Type(Type::BUILTIN_TYPE_STRING))), + array('bigint', array(new Type(Type::BUILTIN_TYPE_STRING))), + array('float', array(new Type(Type::BUILTIN_TYPE_FLOAT))), + array('decimal', array(new Type(Type::BUILTIN_TYPE_STRING))), + array('bool', array(new Type(Type::BUILTIN_TYPE_BOOL))), + array('binary', array(new Type(Type::BUILTIN_TYPE_RESOURCE))), + array('json', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))), + array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))), + array('bar', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_INT), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), + array('indexedBar', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_STRING), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), + array('indexedFoo', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_STRING), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), + array('simpleArray', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), + array('customFoo', null), + array('notMapped', null), + ); + } + + public function testGetPropertiesCatchException() + { + $this->assertNull($this->extractor->getProperties('Not\Exist')); + } + + public function testGetTypesCatchException() + { + $this->assertNull($this->extractor->getTypes('Not\Exist', 'baz')); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php new file mode 100644 index 0000000000000..60fce8954cec3 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\OneToMany; +use Doctrine\ORM\Mapping\ManyToMany; +use Doctrine\ORM\Mapping\ManyToOne; + +/** + * @Entity + * + * @author Kévin Dunglas + */ +class DoctrineDummy +{ + /** + * @Id + * @Column(type="smallint") + */ + public $id; + + /** + * @ManyToOne(targetEntity="DoctrineRelation") + */ + public $foo; + + /** + * @ManyToMany(targetEntity="DoctrineRelation") + */ + public $bar; + + /** + * @ManyToMany(targetEntity="DoctrineRelation", indexBy="rguid") + */ + protected $indexedBar; + + /** + * @OneToMany(targetEntity="DoctrineRelation", mappedBy="foo", indexBy="foo") + */ + protected $indexedFoo; + + /** + * @Column(type="guid") + */ + protected $guid; + + /** + * @Column(type="time") + */ + private $time; + + /** + * @Column(type="json_array") + */ + private $json; + + /** + * @Column(type="simple_array") + */ + private $simpleArray; + + /** + * @Column(type="float") + */ + private $float; + + /** + * @Column(type="decimal", precision=10, scale=2) + */ + private $decimal; + + /** + * @Column(type="boolean") + */ + private $bool; + + /** + * @Column(type="binary") + */ + private $binary; + + /** + * @Column(type="custom_foo") + */ + private $customFoo; + + /** + * @Column(type="bigint") + */ + private $bigint; + + public $notMapped; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php new file mode 100644 index 0000000000000..a00856ed7331e --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Embeddable; + +/** + * @Embeddable + * + * @author Udaltsov Valentin + */ +class DoctrineEmbeddable +{ + /** + * @Column(type="string") + */ + protected $field; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php new file mode 100644 index 0000000000000..394eaebdefc6f --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Type; + +/** + * @author Teoh Han Hui + */ +class DoctrineFooType extends Type +{ + /** + * Type name. + */ + const NAME = 'foo'; + + /** + * {@inheritdoc} + */ + public function getName() + { + return self::NAME; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL(array()); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return; + } + if (!$value instanceof Foo) { + throw new ConversionException(sprintf('Expected %s, got %s', 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\Foo', gettype($value))); + } + + return $foo->bar; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return; + } + if (!is_string($value)) { + throw ConversionException::conversionFailed($value, self::NAME); + } + + $foo = new Foo(); + $foo->bar = $value; + + return $foo; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php new file mode 100644 index 0000000000000..85660d3d6b66c --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\ManyToOne; + +/** + * @Entity + * + * @author Kévin Dunglas + */ +class DoctrineRelation +{ + /** + * @Id + * @Column(type="smallint") + */ + public $id; + + /** + * @Column(type="guid") + */ + protected $rguid; + + /** + * @Column(type="guid") + * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedFoo") + */ + protected $foo; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php new file mode 100644 index 0000000000000..a1e011338f0b0 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\Embedded; + +/** + * @Entity + * + * @author Udaltsov Valentin + */ +class DoctrineWithEmbedded +{ + /** + * @Id + * @Column(type="smallint") + */ + public $id; + + /** + * @Embedded(class="DoctrineEmbeddable") + */ + protected $embedded; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/Foo.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/Foo.php new file mode 100644 index 0000000000000..3e4016cc56ab6 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/Foo.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +/** + * @author Teoh Han Hui + */ +class Foo +{ + /** + * @var string + */ + public $bar; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index ac7dc543a1d8a..299138cf795d9 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -54,6 +54,36 @@ public function testLoadUserByUsername() $this->assertSame($user, $provider->loadUserByUsername('user1')); } + public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty() + { + $user = new User(1, 1, 'user1'); + + $repository = $this->getMockBuilder('Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface') + ->disableOriginalConstructor() + ->getMock(); + $repository + ->expects($this->once()) + ->method('loadUserByUsername') + ->with('user1') + ->willReturn($user); + + $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $em + ->expects($this->once()) + ->method('getRepository') + ->with('Symfony\Bridge\Doctrine\Tests\Fixtures\User') + ->willReturn($repository); + + $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User'); + $this->assertSame($user, $provider->loadUserByUsername('user1')); + } + + /** + * @group legacy + * @expectedDeprecation Implementing Symfony\Component\Security\Core\User\UserProviderInterface in a Doctrine repository when using the entity provider is deprecated since Symfony 2.8 and will not be supported in 3.0. Make the repository implement Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface instead. + */ public function testLoadUserByUsernameWithUserProviderRepositoryAndWithoutProperty() { $user = new User(1, 1, 'user1'); @@ -82,9 +112,9 @@ public function testLoadUserByUsernameWithUserProviderRepositoryAndWithoutProper /** * @expectedException \InvalidArgumentException - * @expectedExceptionMessage You must either make the "Symfony\Bridge\Doctrine\Tests\Fixtures\User" entity Doctrine Repository ("Doctrine\ORM\EntityRepository") implement "Symfony\Component\Security\Core\User\UserProviderInterface" or set the "property" option in the corresponding entity provider configuration. + * @expectedExceptionMessage You must either make the "Symfony\Bridge\Doctrine\Tests\Fixtures\User" entity Doctrine Repository ("Doctrine\ORM\EntityRepository") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration. */ - public function testLoadUserByUsernameWithNonUserProviderRepositoryAndWithoutProperty() + public function testLoadUserByUsernameWithNonUserLoaderRepositoryAndWithoutProperty() { $em = DoctrineTestHelper::createTestEntityManager(); $this->createSchema($em); @@ -149,6 +179,39 @@ public function testSupportProxy() $this->assertTrue($provider->supportsClass(get_class($user2))); } + public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided() + { + $repository = $this->getMockBuilder('\Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface')->getMock(); + $repository->expects($this->once()) + ->method('loadUserByUsername') + ->with('name') + ->willReturn( + $this->getMockBuilder('\Symfony\Component\Security\Core\User\UserInterface')->getMock() + ); + + $provider = new EntityUserProvider( + $this->getManager($this->getObjectManager($repository)), + 'Symfony\Bridge\Doctrine\Tests\Fixtures\User' + ); + + $provider->loadUserByUsername('name'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadUserByUserNameShouldDeclineInvalidInterface() + { + $repository = $this->getMockBuilder('\Symfony\Component\Security\Core\User\AdvancedUserInterface')->getMock(); + + $provider = new EntityUserProvider( + $this->getManager($this->getObjectManager($repository)), + 'Symfony\Bridge\Doctrine\Tests\Fixtures\User' + ); + + $provider->loadUserByUsername('name'); + } + private function getManager($em, $name = null) { $manager = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); @@ -160,6 +223,18 @@ private function getManager($em, $name = null) return $manager; } + private function getObjectManager($repository) + { + $em = $this->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager') + ->setMethods(array('getClassMetadata', 'getRepository')) + ->getMockForAbstractClass(); + $em->expects($this->any()) + ->method('getRepository') + ->willReturn($repository); + + return $em; + } + private function createSchema($em) { $schemaTool = new SchemaTool($em); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 510c0f227afcc..c75df0e02b98b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -169,6 +169,7 @@ public function testValidateUniqueness() $this->buildViolation('myMessage') ->atPath('property.path.name') ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -192,6 +193,7 @@ public function testValidateCustomErrorPath() $this->buildViolation('myMessage') ->atPath('property.path.bar') ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -243,6 +245,7 @@ public function testValidateUniquenessWithIgnoreNullDisabled() $this->buildViolation('myMessage') ->atPath('property.path.name') ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -319,6 +322,7 @@ public function testValidateUniquenessWithValidCustomErrorPath() $this->buildViolation('myMessage') ->atPath('property.path.name2') ->setInvalidValue('Bar') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -451,6 +455,7 @@ public function testAssociatedEntity() $this->buildViolation('myMessage') ->atPath('property.path.single') ->setInvalidValue($entity1) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index fc6e213bd772e..315b1654e8f7e 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -23,6 +23,8 @@ */ class UniqueEntity extends Constraint { + const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f'; + public $message = 'This value is already used.'; public $service = 'doctrine.orm.validator.unique'; public $em = null; @@ -31,6 +33,10 @@ class UniqueEntity extends Constraint public $errorPath = null; public $ignoreNull = true; + protected static $errorNames = array( + self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR', + ); + public function getRequiredOptions() { return array('fields'); diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 3effc89ca09af..f000cae75ebe3 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -160,11 +160,13 @@ public function validate($entity, Constraint $constraint) $this->context->buildViolation($constraint->message) ->atPath($errorPath) ->setInvalidValue($invalidValue) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->addViolation(); } else { $this->buildViolation($constraint->message) ->atPath($errorPath) ->setInvalidValue($invalidValue) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->addViolation(); } } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index cd0794f3e4fbc..e5b5a39a38269 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -18,18 +18,20 @@ "require": { "php": ">=5.3.9", "doctrine/common": "~2.4", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/stopwatch": "~2.2", - "symfony/dependency-injection": "~2.2", - "symfony/form": "~2.7.35|^2.8.28", - "symfony/http-kernel": "~2.2", - "symfony/property-access": "~2.3", - "symfony/security": "~2.2", - "symfony/expression-language": "~2.2", - "symfony/validator": "~2.7.25|^2.8.18", - "symfony/translation": "^2.0.5", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/dependency-injection": "~2.2|~3.0.0", + "symfony/form": "^2.8.28|~3.3.10", + "symfony/http-kernel": "~2.2|~3.0.0", + "symfony/property-access": "~2.3|~3.0.0", + "symfony/property-info": "~2.8|3.0", + "symfony/security": "^2.8.31|^3.3.13", + "symfony/expression-language": "~2.2|~3.0.0", + "symfony/validator": "~2.7.25|^2.8.18|~3.2.5", + "symfony/translation": "^2.0.5|~3.0.0", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", "doctrine/orm": "^2.4.5" @@ -40,6 +42,7 @@ "suggest": { "symfony/form": "", "symfony/validator": "", + "symfony/property-info": "", "doctrine/data-fixtures": "", "doctrine/dbal": "", "doctrine/orm": "" @@ -53,7 +56,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php index c959a5a94fcb7..f1046c96a6ad1 100644 --- a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php @@ -35,6 +35,7 @@ public function getLogs() 'priority' => $record['level'], 'priorityName' => $record['level_name'], 'context' => $record['context'], + 'channel' => isset($record['channel']) ? $record['channel'] : '', ); } diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 5bf272afe7c6e..e65fb26e7ef74 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -21,8 +21,11 @@ "symfony/http-kernel": "~2.4" }, "require-dev": { - "symfony/console": "~2.4", - "symfony/event-dispatcher": "~2.2" + "symfony/console": "~2.4|~3.0.0", + "symfony/event-dispatcher": "~2.2|~3.0.0" + }, + "conflict": { + "symfony/http-kernel": ">=3.0" }, "suggest": { "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", @@ -38,7 +41,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php new file mode 100644 index 0000000000000..8bfb9a6211186 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit; + +/** + * @author Nicolas Grekas + */ +class ClockMock +{ + private static $now; + + public static function withClockMock($enable = null) + { + if (null === $enable) { + return null !== self::$now; + } + + self::$now = is_numeric($enable) ? (float) $enable : ($enable ? microtime(true) : null); + } + + public static function time() + { + if (null === self::$now) { + return \time(); + } + + return (int) self::$now; + } + + public static function sleep($s) + { + if (null === self::$now) { + return \sleep($s); + } + + self::$now += (int) $s; + + return 0; + } + + public static function usleep($us) + { + if (null === self::$now) { + return \usleep($us); + } + + self::$now += $us / 1000000; + } + + public static function microtime($asFloat = false) + { + if (null === self::$now) { + return \microtime($asFloat); + } + + if ($asFloat) { + return self::$now; + } + + return sprintf('%0.6f %d', self::$now - (int) self::$now, (int) self::$now); + } + + public static function register($class) + { + $self = get_called_class(); + + $mockedNs = array(substr($class, 0, strrpos($class, '\\'))); + if (strpos($class, '\\Tests\\')) { + $ns = str_replace('\\Tests\\', '\\', $class); + $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); + } + foreach ($mockedNs as $ns) { + if (function_exists($ns.'\time')) { + continue; + } + eval(<<setAccessible(true); + $r->setValue($e, array_slice($trace, 1, $i)); + + echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; + echo "\n".$msg; + echo "\nStack trace:"; + echo "\n".str_replace(' '.getcwd().DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); + echo "\n"; + + exit(1); + } + if ('legacy' !== $group && DeprecationErrorHandler::MODE_WEAK !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; $ref = &$deprecations[$group][$msg][$class.'::'.$method]; ++$ref; } - } elseif ('weak' !== $mode) { + } elseif (DeprecationErrorHandler::MODE_WEAK !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; } @@ -113,10 +144,13 @@ public static function register($mode = false) } register_shutdown_function(function () use ($getMode, &$deprecations, $deprecationHandler, $colorize) { $mode = $getMode(); + if (isset($mode[0]) && '/' === $mode[0]) { + return; + } $currErrorHandler = set_error_handler('var_dump'); restore_error_handler(); - if ('weak' === $mode) { + if (DeprecationErrorHandler::MODE_WEAK === $mode) { $colorize = function ($str) { return $str; }; } if ($currErrorHandler !== $deprecationHandler) { @@ -155,7 +189,7 @@ public static function register($mode = false) $displayDeprecations($deprecations); // store failing status - $isFailing = 'weak' !== $mode && ($deprecations['unsilenced'] || $deprecations['remaining'] || $deprecations['other']); + $isFailing = DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']; // reset deprecations array foreach ($deprecations as $group => $arrayOrInt) { @@ -170,7 +204,7 @@ public static function register($mode = false) } } $displayDeprecations($deprecations); - if ($isFailing || 'weak' !== $mode && ($deprecations['unsilenced'] || $deprecations['remaining'] || $deprecations['other'])) { + if ($isFailing || DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { exit(1); } }); diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php new file mode 100644 index 0000000000000..e7e2d08e3715f --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit; + +use Doctrine\Common\Annotations\AnnotationRegistry; + +if (!class_exists('PHPUnit_Framework_BaseTestListener')) { + return; +} + +/** + * Collects and replays skipped tests. + * + * @author Nicolas Grekas + */ +class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener +{ + private static $globallyEnabled = false; + private $state = -1; + private $skippedFile = false; + private $wasSkipped = array(); + private $isSkipped = array(); + + /** + * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive) + */ + public function __construct(array $mockedNamespaces = array()) + { + $warn = false; + foreach ($mockedNamespaces as $type => $namespaces) { + if (!is_array($namespaces)) { + $namespaces = array($namespaces); + } + if (is_int($type)) { + // @deprecated BC with v2.8 to v3.0 + $type = 'time-sensitive'; + $warn = true; + } + if ('time-sensitive' === $type) { + foreach ($namespaces as $ns) { + ClockMock::register($ns.'\DummyClass'); + } + } + } + if (self::$globallyEnabled) { + $this->state = -2; + } else { + self::$globallyEnabled = true; + if ($warn) { + echo "Clock-mocked namespaces for SymfonyTestsListener need to be nested in a \"time-sensitive\" key. This will be enforced in Symfony 4.0.\n"; + } + } + } + + public function __destruct() + { + if (0 < $this->state) { + file_put_contents($this->skippedFile, 'isSkipped, true).';'); + } + } + + public function globalListenerDisabled() + { + self::$globallyEnabled = false; + $this->state = -1; + } + + public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) + { + $suiteName = $suite->getName(); + + if (-1 === $this->state) { + echo "Testing $suiteName\n"; + $this->state = 0; + + if (!class_exists('Doctrine\Common\Annotations\AnnotationRegistry', false) && class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { + AnnotationRegistry::registerLoader('class_exists'); + } + + if ($this->skippedFile = getenv('SYMFONY_PHPUNIT_SKIPPED_TESTS')) { + $this->state = 1; + + if (file_exists($this->skippedFile)) { + $this->state = 2; + + if (!$this->wasSkipped = require $this->skippedFile) { + echo "All tests already ran successfully.\n"; + $suite->setTests(array()); + } + } + } + $testSuites = array($suite); + for ($i = 0; isset($testSuites[$i]); ++$i) { + foreach ($testSuites[$i]->tests() as $test) { + if ($test instanceof \PHPUnit_Framework_TestSuite) { + if (!class_exists($test->getName(), false)) { + $testSuites[] = $test; + continue; + } + $groups = \PHPUnit_Util_Test::getGroups($test->getName()); + if (in_array('time-sensitive', $groups, true)) { + ClockMock::register($test->getName()); + } + } + } + } + } elseif (2 === $this->state) { + $skipped = array(); + foreach ($suite->tests() as $test) { + if (!$test instanceof \PHPUnit_Framework_TestCase + || isset($this->wasSkipped[$suiteName]['*']) + || isset($this->wasSkipped[$suiteName][$test->getName()])) { + $skipped[] = $test; + } + } + $suite->setTests($skipped); + } + } + + public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) + { + if (0 < $this->state) { + if ($test instanceof \PHPUnit_Framework_TestCase) { + $class = get_class($test); + $method = $test->getName(); + } else { + $class = $test->getName(); + $method = '*'; + } + + $this->isSkipped[$class][$method] = 1; + } + } + + public function startTest(\PHPUnit_Framework_Test $test) + { + if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { + $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName(false)); + + if (in_array('time-sensitive', $groups, true)) { + ClockMock::register(get_class($test)); + ClockMock::withClockMock(true); + } + } + } + + public function endTest(\PHPUnit_Framework_Test $test, $time) + { + if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { + $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName(false)); + + if (in_array('time-sensitive', $groups, true)) { + ClockMock::withClockMock(false); + } + } + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/regexp.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/regexp.phpt new file mode 100644 index 0000000000000..3b7207b85f8ee --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/regexp.phpt @@ -0,0 +1,40 @@ +--TEST-- +Test DeprecationErrorHandler in weak mode +--FILE-- +testLegacyFoo(); + +?> +--EXPECTF-- +Legacy deprecation triggered by FooTestCase::testLegacyFoo: +silenced foo deprecation +Stack trace: +#%A(%d): FooTestCase->testLegacyFoo() +#%d {main} + diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php index 5243044c6b920..7f218af9a39ff 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php @@ -11,11 +11,23 @@ namespace Symfony\Bridge\PhpUnit\TextUI; +if (!class_exists('PHPUnit_TextUI_Command')) { + return; +} + /** * {@inheritdoc} */ class Command extends \PHPUnit_TextUI_Command { + /** + * {@inheritdoc} + */ + protected function createRunner() + { + return new TestRunner($this->arguments['loader']); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php new file mode 100644 index 0000000000000..eaad394d980f0 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\TextUI; + +use Symfony\Bridge\PhpUnit\SymfonyTestsListener; + +if (!class_exists('PHPUnit_TextUI_TestRunner')) { + return; +} + +/** + * {@inheritdoc} + */ +class TestRunner extends \PHPUnit_TextUI_TestRunner +{ + /** + * {@inheritdoc} + */ + protected function handleConfiguration(array &$arguments) + { + $listener = new SymfonyTestsListener(); + + $result = parent::handleConfiguration($arguments); + + $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); + + $registeredLocally = false; + + foreach ($arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $arguments['listeners'][] = $listener; + } + + return $result; + } +} diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index bb98a92961627..1283da08fa408 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -20,7 +20,7 @@ // Enforce a consistent locale setlocale(LC_ALL, 'C'); -if (class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { +if (!class_exists('Doctrine\Common\Annotations\AnnotationRegistry', false) && class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { AnnotationRegistry::registerLoader('class_exists'); } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 8f818d02db265..791f8b6ef210d 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -36,7 +36,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 5151970c85a89..e749ad40e0742 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -54,10 +54,12 @@ public function getProxyFactoryCode(Definition $definition, $id) { $instantiation = 'return'; - if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { + if ($definition->isShared()) { $instantiation .= " \$this->services['$id'] ="; - } elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { - $instantiation .= " \$this->services['$id'] = \$this->scopedServices['$scope']['$id'] ="; + + if (defined('Symfony\Component\DependencyInjection\ContainerInterface::SCOPE_CONTAINER') && ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope(false)) { + $instantiation .= " \$this->scopedServices['$scope']['$id'] ="; + } } $methodName = 'get'.Container::camelize($id).'Service'; diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 667d7b50c9837..7b560bd6e0dda 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -17,11 +17,11 @@ ], "require": { "php": ">=5.3.9", - "symfony/dependency-injection": "~2.3", + "symfony/dependency-injection": "~2.8|~3.0.0", "ocramius/proxy-manager": "~0.4|~1.0|~2.0" }, "require-dev": { - "symfony/config": "~2.3" + "symfony/config": "~2.3|~3.0.0" }, "autoload": { "psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" }, @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/Swiftmailer/composer.json b/src/Symfony/Bridge/Swiftmailer/composer.json index 81b1d570eadc4..2a5be2e73bd5e 100644 --- a/src/Symfony/Bridge/Swiftmailer/composer.json +++ b/src/Symfony/Bridge/Swiftmailer/composer.json @@ -31,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index f86013b0e2476..b7ba43fa3d348 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Twig\Environment; /** @@ -78,10 +79,11 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $twig = $this->getTwigEnvironment(); if (null === $twig) { - $output->writeln('The Twig environment needs to be set.'); + $io->error('The Twig environment needs to be set.'); return 1; } @@ -96,7 +98,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } } $data['tests'] = array_keys($data['tests']); - $output->writeln(json_encode($data)); + $io->writeln(json_encode($data)); return 0; } @@ -114,14 +116,11 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!$items) { continue; } - if ($index > 0) { - $output->writeln(''); - } - $output->writeln(''.ucfirst($type).''); + + $io->section(ucfirst($type)); + ksort($items); - foreach ($items as $item) { - $output->writeln(' '.$item); - } + $io->listing($items); } return 0; diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index a731242ce5403..2c312c58e53ec 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Finder\Finder; use Twig\Environment; use Twig\Error\Error; @@ -84,14 +85,14 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + if (false !== strpos($input->getFirstArgument(), ':l')) { - $output->writeln('The use of "twig:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:twig" instead.'); + $io->caution('The use of "twig:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:twig" instead.'); } - $twig = $this->getTwigEnvironment(); - - if (null === $twig) { - $output->writeln('The Twig environment needs to be set.'); + if (null === $twig = $this->getTwigEnvironment()) { + $io->error('The Twig environment needs to be set.'); return 1; } @@ -108,12 +109,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $template .= fread(STDIN, 1024); } - return $this->display($input, $output, array($this->validate($twig, $template, uniqid('sf_', true)))); + return $this->display($input, $output, $io, array($this->validate($twig, $template, uniqid('sf_', true)))); } $filesInfo = $this->getFilesInfo($twig, $filenames); - return $this->display($input, $output, $filesInfo); + return $this->display($input, $output, $io, $filesInfo); } private function getFilesInfo(Environment $twig, array $filenames) @@ -157,11 +158,11 @@ private function validate(Environment $twig, $template, $file) return array('template' => $template, 'file' => $file, 'valid' => true); } - private function display(InputInterface $input, OutputInterface $output, $files) + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files) { switch ($input->getOption('format')) { case 'txt': - return $this->displayTxt($output, $files); + return $this->displayTxt($output, $io, $files); case 'json': return $this->displayJson($output, $files); default: @@ -169,20 +170,24 @@ private function display(InputInterface $input, OutputInterface $output, $files) } } - private function displayTxt(OutputInterface $output, $filesInfo) + private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInfo) { $errors = 0; foreach ($filesInfo as $info) { if ($info['valid'] && $output->isVerbose()) { - $output->writeln('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$errors; - $this->renderException($output, $info['template'], $info['exception'], $info['file']); + $this->renderException($io, $info['template'], $info['exception'], $info['file']); } } - $output->writeln(sprintf('%d/%d valid files', count($filesInfo) - $errors, count($filesInfo))); + if (0 === $errors) { + $io->success(sprintf('All %d Twig files contain valid syntax.', count($filesInfo))); + } else { + $io->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', count($filesInfo) - $errors, $errors)); + } return min($errors, 1); } @@ -211,20 +216,20 @@ private function renderException(OutputInterface $output, $template, Error $exce $line = $exception->getTemplateLine(); if ($file) { - $output->writeln(sprintf('KO in %s (line %s)', $file, $line)); + $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); } else { - $output->writeln(sprintf('KO (line %s)', $line)); + $output->text(sprintf(' ERROR (line %s)', $line)); } - foreach ($this->getContext($template, $line) as $no => $code) { - $output->writeln(sprintf( + foreach ($this->getContext($template, $line) as $lineNumber => $code) { + $output->text(sprintf( '%s %-6s %s', - $no == $line ? '>>' : ' ', - $no, + $lineNumber === $line ? ' >> ' : ' ', + $lineNumber, $code )); - if ($no == $line) { - $output->writeln(sprintf('>> %s ', $exception->getRawMessage())); + if ($lineNumber === $line) { + $output->text(sprintf(' >> %s ', $exception->getRawMessage())); } } } diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index fc2583bc7b234..302bd4ea2d24a 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -77,8 +77,20 @@ public function getMacroCount() public function getHtmlCallGraph() { $dumper = new HtmlDumper(); - - return new Markup($dumper->dump($this->getProfile()), 'UTF-8'); + $dump = $dumper->dump($this->getProfile()); + + // needed to remove the hardcoded CSS styles + $dump = str_replace(array( + '', + '', + '', + ), array( + '', + '', + '', + ), $dump); + + return new Markup($dump, 'UTF-8'); } public function getProfile() diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index ffa8293eec2b0..193726a684371 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -13,6 +13,7 @@ use Symfony\Component\Security\Acl\Voter\FieldVote; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -40,7 +41,11 @@ public function isGranted($role, $object = null, $field = null) $object = new FieldVote($object, $field); } - return $this->securityChecker->isGranted($role, $object); + try { + return $this->securityChecker->isGranted($role, $object); + } catch (AuthenticationCredentialsNotFoundException $e) { + return false; + } } /** diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 9f1d95f2782f1..e424998d2bd32 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -57,7 +57,7 @@ {%- endif -%} {%- endif -%} @@ -340,7 +345,7 @@ {%- for attrname, attrvalue in attr if 'readonly' != attrname or not read_only -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} @@ -354,7 +359,7 @@ {%- for attrname, attrvalue in attr -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} @@ -368,7 +373,7 @@ {%- for attrname, attrvalue in attr -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} @@ -381,7 +386,7 @@ {%- for attrname, attrvalue in attr -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig new file mode 100644 index 0000000000000..dc7bec9fb6ccd --- /dev/null +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig @@ -0,0 +1,328 @@ +{% extends "form_div_layout.html.twig" %} + +{# Based on Foundation 5 Doc #} +{# Widgets #} + +{% block form_widget_simple -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block textarea_widget -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock %} + +{% block money_widget -%} +
+ {% set prepend = '{{' == money_pattern[0:2] %} + {% if not prepend %} +
+ {{ money_pattern|replace({ '{{ widget }}':''}) }} +
+ {% endif %} +
+ {{- block('form_widget_simple') -}} +
+ {% if prepend %} +
+ {{ money_pattern|replace({ '{{ widget }}':''}) }} +
+ {% endif %} +
+{%- endblock money_widget %} + +{% block percent_widget -%} +
+
+ {{- block('form_widget_simple') -}} +
+
+ % +
+
+{%- endblock percent_widget %} + +{% block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} +
+
{{ form_errors(form.date) }}
+
{{ form_errors(form.time) }}
+
+
+
{{ form_widget(form.date, { datetime: true } ) }}
+
{{ form_widget(form.time, { datetime: true } ) }}
+
+ {% endif %} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or not datetime %} +
+ {% endif %} + {{- date_pattern|replace({ + '{{ year }}': '
' ~ form_widget(form.year) ~ '
', + '{{ month }}': '
' ~ form_widget(form.month) ~ '
', + '{{ day }}': '
' ~ form_widget(form.day) ~ '
', + })|raw -}} + {% if datetime is not defined or not datetime %} +
+ {% endif %} + {% endif %} +{%- endblock date_widget %} + +{% block time_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or false == datetime %} +
+ {% endif %} + {% if with_seconds %} +
{{ form_widget(form.hour) }}
+
+
+
+ : +
+
+ {{ form_widget(form.minute) }} +
+
+
+
+
+
+ : +
+
+ {{ form_widget(form.second) }} +
+
+
+ {% else %} +
{{ form_widget(form.hour) }}
+
+
+
+ : +
+
+ {{ form_widget(form.minute) }} +
+
+
+ {% endif %} + {% if datetime is not defined or false == datetime %} +
+ {% endif %} + {% endif %} +{%- endblock time_widget %} + +{% block choice_widget_collapsed -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + + {% if multiple -%} + {% set attr = attr|merge({style: (attr.style|default('') ~ ' height: auto; background-image: none;')|trim}) %} + {% endif %} + + {% if required and placeholder is none and not placeholder_in_choices and not multiple -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed %} + +{% block choice_widget_expanded -%} + {% if '-inline' in label_attr.class|default('') %} +
    + {% for child in form %} +
  • {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }}
  • + {% endfor %} +
+ {% else %} +
+ {% for child in form %} + {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }} + {% endfor %} +
+ {% endif %} +{%- endblock choice_widget_expanded %} + +{% block checkbox_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if 'checkbox-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} +
+ {{ form_label(form, null, { widget: parent() }) }} +
+ {% endif %} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if 'radio-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} +
+ {{ form_label(form, null, { widget: parent() }) }} +
+ {% endif %} +{%- endblock radio_widget %} + +{# Labels #} + +{% block form_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {% set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) %} + {{- block('form_label') -}} +{%- endblock %} + +{% block checkbox_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label -%} + {% if required %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {% endif %} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if parent_label_class is defined %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ parent_label_class)|trim}) %} + {% endif %} + {% if label is empty %} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {% endif %} + + {{ widget|raw }} + {{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }} + +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} +
+
+ {{ form_label(form) }} + {{ form_widget(form) }} + {{ form_errors(form) }} +
+
+{%- endblock form_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} +
+
+ {{ form_widget(form) }} + {{ form_errors(form) }} +
+
+{%- endblock checkbox_row %} + +{% block radio_row -%} +
+
+ {{ form_widget(form) }} + {{ form_errors(form) }} +
+
+{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form.parent %}{% else %}
{% endif %} + {%- for error in errors -%} + {{ error.message }} + {% if not loop.last %}, {% endif %} + {%- endfor -%} + {% if form.parent %}{% else %}
{% endif %} + {%- endif %} +{%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index 3349e38531cad..19b8cbec8188e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -31,7 +31,7 @@ public function testLintCorrectFile() $ret = $tester->execute(array('filename' => array($filename)), array('verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false)); $this->assertEquals(0, $ret, 'Returns 0 in case of success'); - $this->assertRegExp('/^OK in /', $tester->getDisplay()); + $this->assertRegExp('/^\/\/ OK in /', trim($tester->getDisplay())); } public function testLintIncorrectFile() @@ -42,7 +42,7 @@ public function testLintIncorrectFile() $ret = $tester->execute(array('filename' => array($filename)), array('decorated' => false)); $this->assertEquals(1, $ret, 'Returns 1 in case of error'); - $this->assertRegExp('/^KO in /', $tester->getDisplay()); + $this->assertRegExp('/ERROR in \S+ \(line /', trim($tester->getDisplay())); } /** @@ -65,7 +65,7 @@ public function testLintFileCompileTimeException() $ret = $tester->execute(array('filename' => array($filename)), array('decorated' => false)); $this->assertEquals(1, $ret, 'Returns 1 in case of error'); - $this->assertRegExp('/^KO in /', $tester->getDisplay()); + $this->assertRegExp('/ERROR in \S+ \(line /', trim($tester->getDisplay())); } /** diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php index 3fb3a19c277ef..cfeb37424b9bb 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -116,39 +116,4 @@ protected function setTheme(FormView $view, array $themes) { $this->extension->renderer->setTheme($view, $themes); } - - public function testRange() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testRangeWithMinMaxValues() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testLabelWithoutTranslationOnButton() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testButtonlabelWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index 2a22f7d88e2b3..120b39fd03e32 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -63,6 +63,30 @@ protected function tearDown() $this->extension = null; } + public function testStartTagHasNoActionAttributeWhenActionIsEmpty() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('
', $html); + } + + public function testStartTagHasActionAttributeWhenActionIsZero() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '0', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + public function testMoneyWidgetInIso() { $environment = new Environment(new StubFilesystemLoader(array( @@ -76,7 +100,7 @@ public function testMoneyWidgetInIso() $this->extension->initRuntime($environment); $view = $this->factory - ->createNamed('name', 'money') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\MoneyType') ->createView() ; @@ -141,39 +165,4 @@ protected function setTheme(FormView $view, array $themes) { $this->extension->renderer->setTheme($view, $themes); } - - public function testRange() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testRangeWithMinMaxValues() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testLabelWithoutTranslationOnButton() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testButtonlabelWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index cb81f7bbc70ee..23a5caf3bf7e9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -70,7 +70,7 @@ protected function tearDown() public function testThemeBlockInheritanceUsingUse() { $view = $this->factory - ->createNamed('name', 'email') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType') ->createView() ; @@ -85,7 +85,7 @@ public function testThemeBlockInheritanceUsingUse() public function testThemeBlockInheritanceUsingExtend() { $view = $this->factory - ->createNamed('name', 'email') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType') ->createView() ; @@ -100,7 +100,7 @@ public function testThemeBlockInheritanceUsingExtend() public function testThemeBlockInheritanceUsingDynamicExtend() { $view = $this->factory - ->createNamed('name', 'email') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType') ->createView() ; @@ -146,6 +146,30 @@ public function testIsChoiceSelected($expected, $choice, $value) $this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value)); } + public function testStartTagHasNoActionAttributeWhenActionIsEmpty() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagHasActionAttributeWhenActionIsZero() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '0', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + public function isRootFormProvider() { return array( @@ -175,7 +199,7 @@ public function testMoneyWidgetInIso() $this->extension->initRuntime($environment); $view = $this->factory - ->createNamed('name', 'money') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\MoneyType') ->createView() ; @@ -249,39 +273,4 @@ public static function themeInheritanceProvider() array(array('parent_label.html.twig'), array('child_label.html.twig')), ); } - - public function testRange() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testRangeWithMinMaxValues() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testLabelWithoutTranslationOnButton() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testButtonlabelWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index 1b5f4022daeb3..e7358b1d63e8e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -64,6 +64,30 @@ protected function tearDown() $this->extension = null; } + public function testStartTagHasNoActionAttributeWhenActionIsEmpty() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagHasActionAttributeWhenActionIsZero() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '0', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + protected function renderForm(FormView $view, array $vars = array()) { return (string) $this->extension->renderer->renderBlock($view, 'form', $vars); @@ -117,39 +141,4 @@ protected function setTheme(FormView $view, array $themes) { $this->extension->renderer->setTheme($view, $themes); } - - public function testRange() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testRangeWithMinMaxValues() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testLabelWithoutTranslationOnButton() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testButtonlabelWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index 564ffc0432fe9..a0221393ce91b 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -46,7 +46,7 @@ public function testUnknownFragmentRenderer() ->disableOriginalConstructor() ->getMock() ; - $renderer = new FragmentHandler(array(), false, $context); + $renderer = new FragmentHandler($context); if (method_exists($this, 'expectException')) { $this->expectException('InvalidArgumentException'); @@ -71,7 +71,7 @@ protected function getFragmentHandler($return) $context->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); - return new FragmentHandler(array($strategy), false, $context); + return new FragmentHandler($context, array($strategy), false); } protected function renderTemplate(FragmentHandler $renderer, $template = '{{ render("foo") }}') diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 04e3819176844..48af8a77b4033 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -20,25 +20,25 @@ "twig/twig": "~1.34|~2.4" }, "require-dev": { - "symfony/asset": "~2.7", - "symfony/finder": "~2.3", - "symfony/form": "~2.7.30|^2.8.23", - "symfony/http-foundation": "~2.7.36|^2.8.29", - "symfony/http-kernel": "~2.3", - "symfony/intl": "~2.3", - "symfony/routing": "~2.2", - "symfony/templating": "~2.1", - "symfony/translation": "~2.7", - "symfony/yaml": "^2.0.5", - "symfony/security": "~2.6", - "symfony/security-acl": "~2.6", - "symfony/stopwatch": "~2.2", - "symfony/console": "~2.7", - "symfony/var-dumper": "~2.7.16|^2.8.9", - "symfony/expression-language": "~2.4" + "symfony/asset": "~2.7|~3.0.0", + "symfony/finder": "~2.3|~3.0.0", + "symfony/form": "^2.8.23", + "symfony/http-foundation": "^2.8.29|~3.0.0", + "symfony/http-kernel": "~2.8|~3.0.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.2|~3.0.0", + "symfony/templating": "~2.1|~3.0.0", + "symfony/translation": "~2.7|~3.0.0", + "symfony/yaml": "^2.0.5|~3.0.0", + "symfony/security": "^2.8.31|^3.3.13", + "symfony/security-acl": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/console": "~2.8|~3.0.0", + "symfony/var-dumper": "~2.7.16|~2.8.9|~3.0.9", + "symfony/expression-language": "~2.4|~3.0.0" }, "conflict": { - "symfony/form": "<2.7.30|~2.8,<2.8.23" + "symfony/form": "<2.8.23" }, "suggest": { "symfony/finder": "", @@ -63,7 +63,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/DebugBundle/Resources/config/services.xml b/src/Symfony/Bundle/DebugBundle/Resources/config/services.xml index 37b7af91a03a0..38635b815a77e 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/DebugBundle/Resources/config/services.xml @@ -11,7 +11,7 @@ - + null %kernel.charset% diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig index e7a8245fb3b04..1163d283d0207 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig @@ -1,21 +1,16 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} {% block toolbar %} - {% set dumps_count = collector.dumpsCount %} - - {% if dumps_count %} + {% if collector.dumpsCount %} {% set icon %} - - {{ dumps_count }} + {{ include('@Debug/Profiler/icon.svg') }} + {{ collector.dumpsCount }} {% endset %} {% set text %} -
- dump() -
{% for dump in collector.getDumps('html') %}
- in + {% if dump.file %} {% set link = dump.file|file_link(dump.line) %} {% if link %} @@ -26,54 +21,32 @@ {% else %} {{ dump.name }} {% endif %} - line {{ dump.line }}: + + line {{ dump.line }} + {{ dump.data|raw }}
{% endfor %} {% endset %} - {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': true } %} + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }} {% endif %} {% endblock %} {% block menu %} - - - {{- "" -}} - - {{- "" -}} - - dump() - - {{ collector.dumpsCount }} - + + {{ include('@Debug/Profiler/icon.svg') }} + Debug {% endblock %} {% block panel %} -

dump()

+

Dumped Contents

- - - {% if collector.dumpsCount %} -
    - {% for dump in collector.getDumps('html') %} -
  • - in + {% for dump in collector.getDumps('html') %} +
    +

    In {% if dump.line %} {% set link = dump.file|file_link(dump.line) %} {% if link %} @@ -84,19 +57,22 @@ {% else %} {{ dump.name }} {% endif %} - line {{ dump.line }}: - - - {% if dump.fileExcerpt %}{{ dump.fileExcerpt|raw }}{% else %}{{ dump.file|file_excerpt(dump.line) }}{% endif %} - + line {{ dump.line }} - {{ dump.data|raw }} -

  • - {% endfor %} -
+ Show code + + + + + {{ dump.data|raw }} + {% else %} -

- No dumped variable -

- {% endif %} +
+

No content was dumped.

+
+ {% endfor %} {% endblock %} diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg new file mode 100644 index 0000000000000..ac4ead8fbbf0f --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php index 3dbe29e9bd8c3..9c03e9988224a 100644 --- a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php @@ -29,6 +29,7 @@ public function testLoadWithoutConfiguration() array( 'id' => 'dump', 'template' => '@Debug/Profiler/dump.html.twig', + 'priority' => 240, ), ); diff --git a/src/Symfony/Bundle/DebugBundle/composer.json b/src/Symfony/Bundle/DebugBundle/composer.json index 3fde4a67247eb..98305f79dcbcc 100644 --- a/src/Symfony/Bundle/DebugBundle/composer.json +++ b/src/Symfony/Bundle/DebugBundle/composer.json @@ -18,14 +18,14 @@ "require": { "php": ">=5.3.9", "ext-xml": "*", - "symfony/http-kernel": "~2.6", - "symfony/twig-bridge": "~2.6", - "symfony/var-dumper": "~2.6" + "symfony/http-kernel": "~2.6|~3.0.0", + "symfony/twig-bridge": "~2.6|~3.0.0", + "symfony/var-dumper": "~2.6|~3.0.0" }, "require-dev": { - "symfony/config": "~2.3", - "symfony/dependency-injection": "~2.3", - "symfony/web-profiler-bundle": "~2.3" + "symfony/config": "~2.3|~3.0.0", + "symfony/dependency-injection": "~2.3|~3.0.0", + "symfony/web-profiler-bundle": "~2.3|~3.0.0" }, "suggest": { "symfony/config": "For service container configuration", @@ -40,7 +40,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index ad8cb2e6e988f..e7c351813ebc8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +2.8.0 +----- + + * Deprecated the `alias` option of the `form.type_extension` tag in favor of the + `extended_type`/`extended-type` option + * Deprecated the `alias` option of the `form.type` tag + * Deprecated the Shell + 2.7.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ClassCacheCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ClassCacheCacheWarmer.php new file mode 100644 index 0000000000000..54b9b3395eace --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ClassCacheCacheWarmer.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Symfony\Component\ClassLoader\ClassCollectionLoader; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; + +/** + * Generates the Class Cache (classes.php) file. + * + * @author Tugdual Saunier + */ +class ClassCacheCacheWarmer implements CacheWarmerInterface +{ + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir) + { + $classmap = $cacheDir.'/classes.map'; + + if (!is_file($classmap)) { + return; + } + + if (file_exists($cacheDir.'/classes.php')) { + return; + } + + ClassCollectionLoader::load(include($classmap), $cacheDir, 'classes', false); + } + + /** + * Checks whether this warmer is optional or not. + * + * @return bool always true + */ + public function isOptional() + { + return true; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php index e49bf22a35627..5aa8ff46ea8f6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Templating\TemplateNameParserInterface; +use Symfony\Component\Templating\TemplateReferenceInterface; use Symfony\Component\HttpKernel\Bundle\BundleInterface; /** @@ -43,7 +44,7 @@ public function __construct(KernelInterface $kernel, TemplateNameParserInterface /** * Find all the templates in the bundle and in the kernel Resources folder. * - * @return array An array of templates of type TemplateReferenceInterface + * @return TemplateReferenceInterface[] */ public function findAllTemplates() { @@ -67,7 +68,7 @@ public function findAllTemplates() * * @param string $dir The folder where to look for templates * - * @return array An array of templates of type TemplateReferenceInterface + * @return TemplateReferenceInterface[] */ private function findTemplatesInFolder($dir) { @@ -91,7 +92,7 @@ private function findTemplatesInFolder($dir) * * @param BundleInterface $bundle The bundle where to look for templates * - * @return array An array of templates of type TemplateReferenceInterface + * @return TemplateReferenceInterface[] */ private function findTemplatesInBundle(BundleInterface $bundle) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php index 223f0216ba9ad..33f94650449fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TranslationsCacheWarmer.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Translation\TranslatorInterface; @@ -22,11 +23,24 @@ */ class TranslationsCacheWarmer implements CacheWarmerInterface { + private $container; private $translator; - public function __construct(TranslatorInterface $translator) + /** + * TranslationsCacheWarmer constructor. + * + * @param ContainerInterface|TranslatorInterface $container + */ + public function __construct($container) { - $this->translator = $translator; + // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. + if ($container instanceof ContainerInterface) { + $this->container = $container; + } elseif ($container instanceof TranslatorInterface) { + $this->translator = $container; + } else { + throw new \InvalidArgumentException(sprintf('%s only accepts instance of Symfony\Component\DependencyInjection\ContainerInterface or Symfony\Component\Translation\TranslatorInterface as first argument.', __CLASS__)); + } } /** @@ -34,6 +48,10 @@ public function __construct(TranslatorInterface $translator) */ public function warmUp($cacheDir) { + if (null === $this->translator) { + $this->translator = $this->container->get('translator'); + } + if ($this->translator instanceof WarmableInterface) { $this->translator->warmUp($cacheDir); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php index 4e729298c1528..7b396799cd874 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -13,7 +13,6 @@ use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Console\Helper\Table; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; @@ -26,7 +25,7 @@ */ abstract class AbstractConfigCommand extends ContainerDebugCommand { - protected function listBundles(OutputInterface $output) + protected function listBundles($output) { $headers = array('Bundle name', 'Extension alias'); $rows = array(); @@ -41,12 +40,10 @@ protected function listBundles(OutputInterface $output) $rows[] = array($bundle->getName(), $extension ? $extension->getAlias() : ''); } - $message = 'Available registered bundles with their extension alias if available:'; if ($output instanceof StyleInterface) { - $output->writeln(' '.$message); $output->table($headers, $rows); } else { - $output->writeln($message); + $output->writeln('Available registered bundles with their extension alias if available:'); $table = new Table($output); $table->setHeaders($headers)->setRows($rows)->render(); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 27cccd869e9a0..66f7f9ca8b468 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -12,19 +12,32 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; /** * Command that places bundle web assets into a given directory. * * @author Fabien Potencier + * @author Gábor Egyed */ class AssetsInstallCommand extends ContainerAwareCommand { + const METHOD_COPY = 'copy'; + const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; + const METHOD_RELATIVE_SYMLINK = 'relative symlink'; + + /** + * @var Filesystem + */ + private $filesystem; + /** * {@inheritdoc} */ @@ -63,8 +76,6 @@ protected function configure() /** * {@inheritdoc} - * - * @throws \InvalidArgumentException When the target directory does not exist or symlink cannot be used */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -74,84 +85,170 @@ protected function execute(InputInterface $input, OutputInterface $output) throw new \InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $input->getArgument('target'))); } - $filesystem = $this->getContainer()->get('filesystem'); + $this->filesystem = $this->getContainer()->get('filesystem'); // Create the bundles directory otherwise symlink will fail. $bundlesDir = $targetArg.'/bundles/'; - $filesystem->mkdir($bundlesDir, 0777); + $this->filesystem->mkdir($bundlesDir, 0777); - // relative implies symlink - $symlink = $input->getOption('symlink') || $input->getOption('relative'); + $io = new SymfonyStyle($input, $output); + $io->newLine(); - if ($symlink) { - $output->writeln('Trying to install assets as symbolic links.'); + if ($input->getOption('relative')) { + $expectedMethod = self::METHOD_RELATIVE_SYMLINK; + $io->text('Trying to install assets as relative symbolic links.'); + } elseif ($input->getOption('symlink')) { + $expectedMethod = self::METHOD_ABSOLUTE_SYMLINK; + $io->text('Trying to install assets as absolute symbolic links.'); } else { - $output->writeln('Installing assets as hard copies.'); + $expectedMethod = self::METHOD_COPY; + $io->text('Installing assets as hard copies.'); } + $io->newLine(); + + $rows = array(); + $copyUsed = false; + $exitCode = 0; $validAssetDirs = array(); + /** @var BundleInterface $bundle */ foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { - if (is_dir($originDir = $bundle->getPath().'/Resources/public')) { - $assetDir = preg_replace('/bundle$/', '', strtolower($bundle->getName())); - $targetDir = $bundlesDir.$assetDir; - $validAssetDirs[] = $assetDir; - - $output->writeln(sprintf('Installing assets for %s into %s', $bundle->getNamespace(), $targetDir)); - - $filesystem->remove($targetDir); - - if ($symlink) { - if ($input->getOption('relative')) { - $relativeOriginDir = $filesystem->makePathRelative($originDir, realpath($bundlesDir)); - } else { - $relativeOriginDir = $originDir; - } - - try { - $filesystem->symlink($relativeOriginDir, $targetDir); - if (!file_exists($targetDir)) { - throw new IOException('Symbolic link is broken'); - } - $output->writeln('The assets were installed using symbolic links.'); - } catch (IOException $e) { - if (!$input->getOption('relative')) { - $this->hardCopy($originDir, $targetDir); - $output->writeln('It looks like your system doesn\'t support symbolic links, so the assets were installed by copying them.'); - } - - // try again without the relative option - try { - $filesystem->symlink($originDir, $targetDir); - if (!file_exists($targetDir)) { - throw new IOException('Symbolic link is broken'); - } - $output->writeln('It looks like your system doesn\'t support relative symbolic links, so the assets were installed by using absolute symbolic links.'); - } catch (IOException $e) { - $this->hardCopy($originDir, $targetDir); - $output->writeln('It looks like your system doesn\'t support symbolic links, so the assets were installed by copying them.'); - } - } + if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) { + continue; + } + + $assetDir = preg_replace('/bundle$/', '', strtolower($bundle->getName())); + $targetDir = $bundlesDir.$assetDir; + $validAssetDirs[] = $assetDir; + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $message = sprintf("%s\n-> %s", $bundle->getName(), $targetDir); + } else { + $message = $bundle->getName(); + } + + try { + $this->filesystem->remove($targetDir); + + if (self::METHOD_RELATIVE_SYMLINK === $expectedMethod) { + $method = $this->relativeSymlinkWithFallback($originDir, $targetDir); + } elseif (self::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); } else { - $this->hardCopy($originDir, $targetDir); + $method = $this->hardCopy($originDir, $targetDir); + } + + if (self::METHOD_COPY === $method) { + $copyUsed = true; } + + if ($method === $expectedMethod) { + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */), $message, $method); + } else { + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method); + } + } catch (\Exception $e) { + $exitCode = 1; + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage()); } } - // remove the assets of the bundles that no longer exist $dirsToRemove = Finder::create()->depth(0)->directories()->exclude($validAssetDirs)->in($bundlesDir); - $filesystem->remove($dirsToRemove); + $this->filesystem->remove($dirsToRemove); + + $io->table(array('', 'Bundle', 'Method / Error'), $rows); + + if (0 !== $exitCode) { + $io->error('Some errors occurred while installing assets.'); + } else { + if ($copyUsed) { + $io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.'); + } + $io->success('All assets were successfully installed.'); + } + + return $exitCode; } /** + * Try to create relative symlink. + * + * Falling back to absolute symlink and finally hard copy. + * * @param string $originDir * @param string $targetDir + * + * @return string */ - private function hardCopy($originDir, $targetDir) + private function relativeSymlinkWithFallback($originDir, $targetDir) { - $filesystem = $this->getContainer()->get('filesystem'); + try { + $this->symlink($originDir, $targetDir, true); + $method = self::METHOD_RELATIVE_SYMLINK; + } catch (IOException $e) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); + } + + return $method; + } - $filesystem->mkdir($targetDir, 0777); + /** + * Try to create absolute symlink. + * + * Falling back to hard copy. + * + * @param string $originDir + * @param string $targetDir + * + * @return string + */ + private function absoluteSymlinkWithFallback($originDir, $targetDir) + { + try { + $this->symlink($originDir, $targetDir); + $method = self::METHOD_ABSOLUTE_SYMLINK; + } catch (IOException $e) { + // fall back to copy + $method = $this->hardCopy($originDir, $targetDir); + } + + return $method; + } + + /** + * Creates symbolic link. + * + * @param string $originDir + * @param string $targetDir + * @param bool $relative + * + * @throws IOException if link can not be created + */ + private function symlink($originDir, $targetDir, $relative = false) + { + if ($relative) { + $originDir = $this->filesystem->makePathRelative($originDir, realpath(dirname($targetDir))); + } + $this->filesystem->symlink($originDir, $targetDir); + if (!file_exists($targetDir)) { + throw new IOException(sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0, null, $targetDir); + } + } + + /** + * Copies origin to target. + * + * @param string $originDir + * @param string $targetDir + * + * @return string + */ + private function hardCopy($originDir, $targetDir) + { + $this->filesystem->mkdir($targetDir, 0777); // We use a custom iterator to ignore VCS files - $filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + + return self::METHOD_COPY; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index 5e9014bfc5ddd..de1cfc8e2024f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Finder\Finder; @@ -53,6 +54,9 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $outputIsVerbose = $output->isVerbose(); + $io = new SymfonyStyle($input, $output); + $realCacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); // the old cache dir name must not be longer than the real one to avoid exceeding // the maximum length of a directory or file path within it (esp. Windows MAX_PATH) @@ -68,7 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $kernel = $this->getContainer()->get('kernel'); - $output->writeln(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + $io->comment(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); $this->getContainer()->get('cache_clearer')->clear($realCacheDir); if ($input->getOption('no-warmup')) { @@ -80,14 +84,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_'); if ($filesystem->exists($warmupDir)) { - if ($output->isVerbose()) { - $output->writeln(' Clearing outdated warmup directory'); + if ($outputIsVerbose) { + $io->comment('Clearing outdated warmup directory...'); } $filesystem->remove($warmupDir); } - if ($output->isVerbose()) { - $output->writeln(' Warming up cache'); + if ($outputIsVerbose) { + $io->comment('Warming up cache...'); } $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); @@ -98,15 +102,17 @@ protected function execute(InputInterface $input, OutputInterface $output) $filesystem->rename($warmupDir, $realCacheDir); } - if ($output->isVerbose()) { - $output->writeln(' Removing old cache directory'); + if ($outputIsVerbose) { + $io->comment('Removing old cache directory...'); } $filesystem->remove($oldCacheDir); - if ($output->isVerbose()) { - $output->writeln(' Done'); + if ($outputIsVerbose) { + $io->comment('Finished'); } + + $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 54d2fe9c426c5..1e0c4b6b3a251 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Warmup the cache. @@ -53,8 +54,10 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + $kernel = $this->getContainer()->get('kernel'); - $output->writeln(sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + $io->comment(sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); $warmer = $this->getContainer()->get('cache_warmer'); @@ -63,5 +66,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $warmer->warmUp($this->getContainer()->getParameter('kernel.cache_dir')); + + $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index 22a98d8023a4e..9561a2f31d9b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -58,14 +58,16 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); if (false !== strpos($input->getFirstArgument(), ':d')) { - $output->caution('The use of "config:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:config" instead.'); + $io->caution('The use of "config:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:config" instead.'); } $name = $input->getArgument('name'); if (empty($name)) { + $io->comment('Provide the name of a bundle as the first argument of this command to dump its configuration.'); + $io->newLine(); $this->listBundles($output); return; @@ -85,12 +87,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $config = $processor->processConfiguration($configuration, $configs); if ($name === $extension->getAlias()) { - $output->writeln(sprintf('# Current configuration for extension with alias: "%s"', $name)); + $io->title(sprintf('Current configuration for extension with alias "%s"', $name)); } else { - $output->writeln(sprintf('# Current configuration for "%s"', $name)); + $io->title(sprintf('Current configuration for "%s"', $name)); } - $output->writeln(Yaml::dump(array($extension->getAlias() => $container->getParameterBag()->resolveValue($config)), 10)); + $io->writeln(Yaml::dump(array($extension->getAlias() => $container->getParameterBag()->resolveValue($config)), 10)); } private function compileContainer() diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index d9752a0139ff2..1d015c94ac0b4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -67,10 +67,12 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); $name = $input->getArgument('name'); if (empty($name)) { + $io->comment('Provide the name of a bundle as the first argument of this command to dump its default configuration.'); + $io->newLine(); $this->listBundles($output); return; @@ -90,18 +92,18 @@ protected function execute(InputInterface $input, OutputInterface $output) switch ($input->getOption('format')) { case 'yaml': - $output->writeln(sprintf('# %s', $message)); + $io->writeln(sprintf('# %s', $message)); $dumper = new YamlReferenceDumper(); break; case 'xml': - $output->writeln(sprintf('', $message)); + $io->writeln(sprintf('', $message)); $dumper = new XmlReferenceDumper(); break; default: - $output->writeln($message); + $io->writeln($message); throw new \InvalidArgumentException('Only the yaml and xml formats are supported.'); } - $output->writeln($dumper->dump($configuration, $extension->getNamespace())); + $io->writeln($dumper->dump($configuration, $extension->getNamespace())); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 127a75fbcd3fe..fd39399cf5c3e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -16,10 +16,10 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; -use Symfony\Component\Console\Question\ChoiceQuestion; /** * A console command for retrieving information about services. @@ -94,8 +94,9 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); if (false !== strpos($input->getFirstArgument(), ':d')) { - $output->writeln('The use of "container:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:container" instead.'); + $io->caution('The use of "container:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:container" instead.'); } $this->validateInput($input); @@ -111,7 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } elseif ($tag = $input->getOption('tag')) { $options = array('tag' => $tag, 'show_private' => $input->getOption('show-private')); } elseif ($name = $input->getArgument('name')) { - $name = $this->findProperServiceName($input, $output, $object, $name); + $name = $this->findProperServiceName($input, $io, $object, $name); $options = array('id' => $name); } else { $options = array('show_private' => $input->getOption('show-private')); @@ -120,10 +121,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $helper = new DescriptorHelper(); $options['format'] = $input->getOption('format'); $options['raw_text'] = $input->getOption('raw'); + $options['output'] = $io; $helper->describe($output, $object, $options); if (!$input->getArgument('name') && $input->isInteractive()) { - $output->writeln('To search for a service, re-run this command with a search term. debug:container log'); + $io->comment('To search for a specific service, re-run this command with a search term. (e.g. debug:container log)'); } } @@ -180,7 +182,7 @@ protected function getContainerBuilder() return $this->containerBuilder = $container; } - private function findProperServiceName(InputInterface $input, OutputInterface $output, ContainerBuilder $builder, $name) + private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, $name) { if ($builder->has($name) || !$input->isInteractive()) { return $name; @@ -191,10 +193,7 @@ private function findProperServiceName(InputInterface $input, OutputInterface $o throw new \InvalidArgumentException(sprintf('No services found that match "%s".', $name)); } - $question = new ChoiceQuestion('Choose a number for more information on the service', $matchingServices); - $question->setErrorMessage('Service %s is invalid.'); - - return $this->getHelper('question')->ask($input, $output, $question); + return $io->choice('Select one of the following services to display its information', $matchingServices); } private function findServiceIdsContaining(ContainerBuilder $builder, $name) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index f9bc810d0fa27..2eb310ddf1701 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -58,32 +59,25 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $dispatcher = $this->getEventDispatcher(); + $options = array(); if ($event = $input->getArgument('event')) { if (!$dispatcher->hasListeners($event)) { - $formatter = $this->getHelperSet()->get('formatter'); - - $formattedBlock = $formatter->formatBlock( - sprintf('[NOTE] The event "%s" does not have any registered listeners.', $event), - 'fg=yellow', - true - ); - - $output->writeln($formattedBlock); + $io->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); return; } $options = array('event' => $event); - } else { - $options = array(); } $helper = new DescriptorHelper(); $options['format'] = $input->getOption('format'); $options['raw_text'] = $input->getOption('raw'); - $helper->describe($output, $dispatcher, $options); + $options['output'] = $io; + $helper->describe($io, $dispatcher, $options); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php index d90e2378658bf..ec45ef04fe5fa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Routing\Matcher\Dumper\ApacheMatcherDumper; use Symfony\Component\Routing\RouterInterface; @@ -74,9 +75,10 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $formatter = $this->getHelper('formatter'); + $io = new SymfonyStyle($input, $output); - $output->writeln($formatter->formatSection('warning', 'The router:dump-apache command is deprecated since version 2.5 and will be removed in 3.0', 'comment')); + $io->title('Router Apache Dumper'); + $io->caution('The router:dump-apache command is deprecated since version 2.5 and will be removed in 3.0.'); $router = $this->getContainer()->get('router'); @@ -90,6 +92,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $dumper = new ApacheMatcherDumper($router->getRouteCollection()); - $output->writeln($dumper->dump($dumpOptions), OutputInterface::OUTPUT_RAW); + $io->writeln($dumper->dump($dumpOptions), OutputInterface::OUTPUT_RAW); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index eb4407b8f8894..608f7a0fb4b62 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\Route; @@ -77,8 +78,10 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + if (false !== strpos($input->getFirstArgument(), ':d')) { - $output->writeln('The use of "router:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:router" instead.'); + $io->caution('The use of "router:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:router" instead.'); } $name = $input->getArgument('name'); @@ -89,21 +92,25 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!$route = $routes->get($name)) { throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); } + $this->convertController($route); - $helper->describe($output, $route, array( + + $helper->describe($io, $route, array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'name' => $name, + 'output' => $io, )); } else { foreach ($routes as $route) { $this->convertController($route); } - $helper->describe($output, $routes, array( + $helper->describe($io, $routes, array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), + 'output' => $io, )); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index 92cdee844fa3e..2e23ad72c2aef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -11,11 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; @@ -60,7 +61,7 @@ protected function configure() The %command.name% shows which routes match a given request and which don't and for what reason: php %command.full_name% /foo - + or php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose @@ -75,6 +76,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + $router = $this->getContainer()->get('router'); $context = $router->getContext(); if (null !== $method = $input->getOption('method')) { @@ -91,25 +94,26 @@ protected function execute(InputInterface $input, OutputInterface $output) $traces = $matcher->getTraces($input->getArgument('path_info')); + $io->newLine(); + $matches = false; foreach ($traces as $trace) { if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) { - $output->writeln(sprintf('Route "%s" almost matches but %s', $trace['name'], lcfirst($trace['log']))); + $io->text(sprintf('Route "%s" almost matches but %s', $trace['name'], lcfirst($trace['log']))); } elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) { - $output->writeln(sprintf('Route "%s" matches', $trace['name'])); + $io->success(sprintf('Route "%s" matches', $trace['name'])); - $routerDebugcommand = $this->getApplication()->find('debug:router'); - $output->writeln(''); - $routerDebugcommand->run(new ArrayInput(array('name' => $trace['name'])), $output); + $routerDebugCommand = $this->getApplication()->find('debug:router'); + $routerDebugCommand->run(new ArrayInput(array('name' => $trace['name'])), $output); $matches = true; } elseif ($input->getOption('verbose')) { - $output->writeln(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log'])); + $io->text(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log'])); } } if (!$matches) { - $output->writeln(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info'))); + $io->error(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info'))); return 1; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php index 4c2b214a31304..ef5c0bbbe0f09 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\ProcessBuilder; @@ -32,7 +33,8 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'), + new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'), new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null), new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'), )) @@ -71,6 +73,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $documentRoot = $input->getOption('docroot'); if (null === $documentRoot) { @@ -78,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!is_dir($documentRoot)) { - $output->writeln(sprintf('The given document root directory "%s" does not exist', $documentRoot)); + $io->error(sprintf('The given document root directory "%s" does not exist', $documentRoot)); return 1; } @@ -87,25 +90,23 @@ protected function execute(InputInterface $input, OutputInterface $output) $address = $input->getArgument('address'); if (false === strpos($address, ':')) { - $output->writeln('The address has to be of the form bind-address:port.'); - - return 1; + $address = $address.':'.$input->getOption('port'); } if ($this->isOtherServerProcessRunning($address)) { - $output->writeln(sprintf('A process is already listening on http://%s.', $address)); + $io->error(sprintf('A process is already listening on http://%s.', $address)); return 1; } if ('prod' === $env) { - $output->writeln('Running PHP built-in server in production environment is NOT recommended!'); + $io->error('Running PHP built-in server in production environment is NOT recommended!'); } - $output->writeln(sprintf("Server running on http://%s\n", $address)); - $output->writeln('Quit the server with CONTROL-C.'); + $io->success(sprintf('Server running on http://%s', $address)); + $io->comment('Quit the server with CONTROL-C.'); - if (null === $builder = $this->createPhpProcessBuilder($output, $address, $input->getOption('router'), $env)) { + if (null === $builder = $this->createPhpProcessBuilder($io, $address, $input->getOption('router'), $env)) { return 1; } @@ -122,17 +123,19 @@ protected function execute(InputInterface $input, OutputInterface $output) ->run($output, $process, null, null, OutputInterface::VERBOSITY_VERBOSE); if (!$process->isSuccessful()) { - $output->writeln('Built-in server terminated unexpectedly'); + $errorMessages = array('Built-in server terminated unexpectedly.'); if ($process->isOutputDisabled()) { - $output->writeln('Run the command again with -v option for more details'); + $errorMessages[] = 'Run the command again with -v option for more details.'; } + + $io->error($errorMessages); } return $process->getExitCode(); } - private function createPhpProcessBuilder(OutputInterface $output, $address, $router, $env) + private function createPhpProcessBuilder(SymfonyStyle $io, $address, $router, $env) { $router = $router ?: $this ->getContainer() @@ -141,7 +144,7 @@ private function createPhpProcessBuilder(OutputInterface $output, $address, $rou ; if (!file_exists($router)) { - $output->writeln(sprintf('The given router script "%s" does not exist', $router)); + $io->error(sprintf('The given router script "%s" does not exist.', $router)); return; } @@ -150,7 +153,7 @@ private function createPhpProcessBuilder(OutputInterface $output, $address, $rou $finder = new PhpExecutableFinder(); if (false === $binary = $finder->find()) { - $output->writeln('Unable to find PHP binary to run server'); + $io->error('Unable to find PHP binary to run server.'); return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php index 2c45de91680f9..5e2f273ac8784 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php @@ -11,11 +11,11 @@ namespace Symfony\Bundle\FrameworkBundle\Command; -use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; @@ -33,9 +33,11 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'), + new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'), new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null), new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'), + new InputOption('force', 'f', InputOption::VALUE_NONE, 'Force web server startup'), )) ->setName('server:start') ->setDescription('Starts PHP built-in web server in the background') @@ -72,14 +74,18 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $cliOutput = $output); + if (!extension_loaded('pcntl')) { - $output->writeln('This command needs the pcntl extension to run.'); - $output->writeln('You can either install it or use the server:run command instead to run the built-in web server.'); + $io->error(array( + 'This command needs the pcntl extension to run.', + 'You can either install it or use the "server:run" command instead to run the built-in web server.', + )); - if ($this->getHelper('question')->ask($input, $output, new ConfirmationQuestion('Do you want to start server:run immediately? [Yn] ', true))) { + if ($io->ask('Do you want to execute server:run immediately? [Yn] ', true)) { $command = $this->getApplication()->find('server:run'); - return $command->run($input, $output); + return $command->run($input, $cliOutput); } return 1; @@ -92,56 +98,57 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!is_dir($documentRoot)) { - $output->writeln(sprintf('The given document root directory "%s" does not exist', $documentRoot)); + $io->error(sprintf('The given document root directory "%s" does not exist.', $documentRoot)); return 1; } $env = $this->getContainer()->getParameter('kernel.environment'); - if (false === $router = $this->determineRouterScript($input->getOption('router'), $env, $output)) { + if (false === $router = $this->determineRouterScript($input->getOption('router'), $env, $io)) { return 1; } $address = $input->getArgument('address'); if (false === strpos($address, ':')) { - $output->writeln('The address has to be of the form bind-address:port.'); - - return 1; + $address = $address.':'.$input->getOption('port'); } - if ($this->isOtherServerProcessRunning($address)) { - $output->writeln(sprintf('A process is already listening on http://%s.', $address)); + if (!$input->getOption('force') && $this->isOtherServerProcessRunning($address)) { + $io->error(array( + sprintf('A process is already listening on http://%s.', $address), + 'Use the --force option if the server process terminated unexpectedly to start a new web server process.', + )); return 1; } if ('prod' === $env) { - $output->writeln('Running PHP built-in server in production environment is NOT recommended!'); + $io->error('Running PHP built-in server in production environment is NOT recommended!'); } $pid = pcntl_fork(); if ($pid < 0) { - $output->writeln('Unable to start the server process'); + $io->error('Unable to start the server process.'); return 1; } if ($pid > 0) { - $output->writeln(sprintf('Web server listening on http://%s', $address)); + $io->success(sprintf('Web server listening on http://%s', $address)); return; } if (posix_setsid() < 0) { - $output->writeln('Unable to set the child process as session leader'); + $io->error('Unable to set the child process as session leader'); return 1; } - if (null === $process = $this->createServerProcess($output, $address, $documentRoot, $router)) { + if (null === $process = $this->createServerProcess($io, $address, $documentRoot, $router)) { return 1; } @@ -151,7 +158,7 @@ protected function execute(InputInterface $input, OutputInterface $output) touch($lockFile); if (!$process->isRunning()) { - $output->writeln('Unable to start the server process'); + $io->error('Unable to start the server process'); unlink($lockFile); return 1; @@ -171,13 +178,13 @@ protected function execute(InputInterface $input, OutputInterface $output) * Determine the absolute file path for the router script, using the environment to choose a standard script * if no custom router script is specified. * - * @param string|null $router File path of the custom router script, if set by the user; otherwise null - * @param string $env The application environment - * @param OutputInterface $output An OutputInterface instance + * @param string|null $router File path of the custom router script, if set by the user; otherwise null + * @param string $env The application environment + * @param SymfonyStyle $io An SymfonyStyle instance * * @return string|bool The absolute file path of the router script, or false on failure */ - private function determineRouterScript($router, $env, OutputInterface $output) + private function determineRouterScript($router, $env, SymfonyStyle $io) { if (null === $router) { $router = $this @@ -188,7 +195,7 @@ private function determineRouterScript($router, $env, OutputInterface $output) } if (false === $path = realpath($router)) { - $output->writeln(sprintf('The given router script "%s" does not exist', $router)); + $io->error(sprintf('The given router script "%s" does not exist.', $router)); return false; } @@ -199,18 +206,18 @@ private function determineRouterScript($router, $env, OutputInterface $output) /** * Creates a process to start PHP's built-in web server. * - * @param OutputInterface $output A OutputInterface instance - * @param string $address IP address and port to listen to - * @param string $documentRoot The application's document root - * @param string $router The router filename + * @param SymfonyStyle $io A SymfonyStyle instance + * @param string $address IP address and port to listen to + * @param string $documentRoot The application's document root + * @param string $router The router filename * * @return Process The process */ - private function createServerProcess(OutputInterface $output, $address, $documentRoot, $router) + private function createServerProcess(SymfonyStyle $io, $address, $documentRoot, $router) { $finder = new PhpExecutableFinder(); if (false === $binary = $finder->find()) { - $output->writeln('Unable to find PHP binary to start server'); + $io->error('Unable to find PHP binary to start server.'); return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php index 58871896c651d..31f7078b1da08 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Shows the status of a process that is running PHP's built-in web server in @@ -44,6 +45,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); $address = $input->getArgument('address'); if (false === strpos($address, ':')) { @@ -56,9 +58,9 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (file_exists($this->getLockFile($address))) { - $output->writeln(sprintf('Web server still listening on http://%s', $address)); + $io->success(sprintf('Web server still listening on http://%s', $address)); } else { - $output->writeln(sprintf('No web server is listening on http://%s', $address)); + $io->warning(sprintf('No web server is listening on http://%s', $address)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php index 84ed54c41e7a6..8f79978a9a845 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php @@ -14,6 +14,8 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Stops a background process running PHP's built-in web server. @@ -29,7 +31,8 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'), + new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'), )) ->setName('server:stop') ->setDescription('Stops PHP\'s built-in web server that was started with the server:start command') @@ -52,16 +55,22 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + $address = $input->getArgument('address'); + if (false === strpos($address, ':')) { + $address = $address.':'.$input->getOption('port'); + } + $lockFile = $this->getLockFile($address); if (!file_exists($lockFile)) { - $output->writeln(sprintf('No web server is listening on http://%s', $address)); + $io->error(sprintf('No web server is listening on http://%s', $address)); return 1; } unlink($lockFile); - $output->writeln(sprintf('Stopped the web server listening on http://%s', $address)); + $io->success(sprintf('Stopped the web server listening on http://%s', $address)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index be4696adfd806..4404f3a0f6963 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -11,12 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\DataCollectorTranslator; @@ -50,6 +52,7 @@ protected function configure() new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'), new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Displays only missing messages'), new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Displays only unused messages'), + new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), )) ->setDescription('Displays translation messages information') ->setHelp(<<<'EOF' @@ -77,6 +80,10 @@ protected function configure() php %command.full_name% en +You can display information about translations in all registered bundles in a specific locale: + + php %command.full_name% --all en + EOF ) ; @@ -87,14 +94,16 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); if (false !== strpos($input->getFirstArgument(), ':d')) { - $output->caution('The use of "translation:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:translation" instead.'); + $io->caution('The use of "translation:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:translation" instead.'); } $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); + /** @var TranslationLoader $loader */ $loader = $this->getContainer()->get('translation.loader'); + /** @var Kernel $kernel */ $kernel = $this->getContainer()->get('kernel'); // Define Root Path to App folder @@ -111,30 +120,23 @@ protected function execute(InputInterface $input, OutputInterface $output) } catch (\InvalidArgumentException $e) { // such a bundle does not exist, so treat the argument as path $transPaths = array($input->getArgument('bundle').'/Resources/'); + if (!is_dir($transPaths[0])) { throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); } } + } elseif ($input->getOption('all')) { + foreach ($kernel->getBundles() as $bundle) { + $transPaths[] = $bundle->getPath().'/Resources/'; + $transPaths[] = sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName()); + } } // Extract used messages - $extractedCatalogue = new MessageCatalogue($locale); - foreach ($transPaths as $path) { - $path .= 'views'; - - if (is_dir($path)) { - $this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue); - } - } + $extractedCatalogue = $this->extractMessages($locale, $transPaths); // Load defined messages - $currentCatalogue = new MessageCatalogue($locale); - foreach ($transPaths as $path) { - $path .= 'translations'; - if (is_dir($path)) { - $loader->loadMessages($path, $currentCatalogue); - } - } + $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths, $loader); // Merge defined and extracted messages to get all message ids $mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue); @@ -151,30 +153,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $outputMessage .= sprintf(' and domain "%s"', $domain); } - $output->warning($outputMessage); + $io->warning($outputMessage); return; } // Load the fallback catalogues - $fallbackCatalogues = array(); - $translator = $this->getContainer()->get('translator'); - if ($translator instanceof Translator || $translator instanceof DataCollectorTranslator || $translator instanceof LoggingTranslator) { - foreach ($translator->getFallbackLocales() as $fallbackLocale) { - if ($fallbackLocale === $locale) { - continue; - } - - $fallbackCatalogue = new MessageCatalogue($fallbackLocale); - foreach ($transPaths as $path) { - $path = $path.'translations'; - if (is_dir($path)) { - $loader->loadMessages($path, $fallbackCatalogue); - } - } - $fallbackCatalogues[] = $fallbackCatalogue; - } - } + $fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths, $loader); // Display header line $headers = array('State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale)); @@ -218,21 +203,21 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $output->table($headers, $rows); + $io->table($headers, $rows); } private function formatState($state) { if (self::MESSAGE_MISSING === $state) { - return 'missing'; + return ' missing '; } if (self::MESSAGE_UNUSED === $state) { - return 'unused'; + return ' unused '; } if (self::MESSAGE_EQUALS_FALLBACK === $state) { - return 'fallback'; + return ' fallback '; } return $state; @@ -250,14 +235,14 @@ private function formatStates(array $states) private function formatId($id) { - return sprintf('%s', $id); + return sprintf('%s', $id); } private function sanitizeString($string, $length = 40) { $string = trim(preg_replace('/\s+/', ' ', $string)); - if (function_exists('mb_strlen') && false !== $encoding = mb_detect_encoding($string)) { + if (false !== $encoding = mb_detect_encoding($string, null, true)) { if (mb_strlen($string, $encoding) > $length) { return mb_substr($string, 0, $length - 3, $encoding).'...'; } @@ -267,4 +252,74 @@ private function sanitizeString($string, $length = 40) return $string; } + + /** + * @param string $locale + * @param array $transPaths + * + * @return MessageCatalogue + */ + private function extractMessages($locale, $transPaths) + { + $extractedCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + $path = $path.'views'; + if (is_dir($path)) { + $this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue); + } + } + + return $extractedCatalogue; + } + + /** + * @param string $locale + * @param array $transPaths + * @param TranslationLoader $loader + * + * @return MessageCatalogue + */ + private function loadCurrentMessages($locale, $transPaths, TranslationLoader $loader) + { + $currentCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $loader->loadMessages($path, $currentCatalogue); + } + } + + return $currentCatalogue; + } + + /** + * @param string $locale + * @param array $transPaths + * @param TranslationLoader $loader + * + * @return MessageCatalogue[] + */ + private function loadFallbackCatalogues($locale, $transPaths, TranslationLoader $loader) + { + $fallbackCatalogues = array(); + $translator = $this->getContainer()->get('translator'); + if ($translator instanceof Translator || $translator instanceof DataCollectorTranslator || $translator instanceof LoggingTranslator) { + foreach ($translator->getFallbackLocales() as $fallbackLocale) { + if ($fallbackLocale === $locale) { + continue; + } + + $fallbackCatalogue = new MessageCatalogue($fallbackLocale); + foreach ($transPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $loader->loadMessages($path, $fallbackCatalogue); + } + } + $fallbackCatalogues[] = $fallbackCatalogue; + } + } + + return $fallbackCatalogues; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 3f1605da582b9..e0c7d380905f1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -12,7 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Translation\Catalogue\DiffOperation; +use Symfony\Component\Translation\Catalogue\TargetOperation; use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -21,7 +21,8 @@ use Symfony\Component\Translation\MessageCatalogue; /** - * A command that parse templates to extract translation messages and add them into the translation files. + * A command that parses templates to extract translation messages and adds them + * into the translation files. * * @author Michel Salib */ @@ -69,11 +70,11 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); // check presence of force or dump-message if (true !== $input->getOption('force') && true !== $input->getOption('dump-messages')) { - $output->error('You must choose one of --force or --dump-messages'); + $io->error('You must choose one of --force or --dump-messages'); return 1; } @@ -82,7 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $writer = $this->getContainer()->get('translation.writer'); $supportedFormats = $writer->getFormats(); if (!in_array($input->getOption('output-format'), $supportedFormats)) { - $output->error(array('Wrong output format', 'Supported formats are '.implode(', ', $supportedFormats).'.')); + $io->error(array('Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).'.')); return 1; } @@ -112,12 +113,12 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $output->title('Symfony translation update command'); - $output->text(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); + $io->title('Translation Messages Extractor and Dumper'); + $io->comment(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); // load any messages from templates $extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); - $output->text('Parsing templates'); + $io->comment('Parsing templates...'); $extractor = $this->getContainer()->get('translation.extractor'); $extractor->setPrefix($input->getOption('prefix')); foreach ($transPaths as $path) { @@ -129,7 +130,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // load any existing messages from the translation files $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); - $output->text('Loading translation files'); + $io->comment('Loading translation files...'); $loader = $this->getContainer()->get('translation.loader'); foreach ($transPaths as $path) { $path .= 'translations'; @@ -140,24 +141,27 @@ protected function execute(InputInterface $input, OutputInterface $output) // process catalogues $operation = $input->getOption('clean') - ? new DiffOperation($currentCatalogue, $extractedCatalogue) + ? new TargetOperation($currentCatalogue, $extractedCatalogue) : new MergeOperation($currentCatalogue, $extractedCatalogue); // Exit if no messages found. if (!count($operation->getDomains())) { - $output->warning('No translation found.'); + $io->warning('No translation messages were found.'); return; } + $resultMessage = 'Translation files were successfully updated'; + // show compiled list of messages if (true === $input->getOption('dump-messages')) { - $output->newLine(); + $extractedMessagesCount = 0; + $io->newLine(); foreach ($operation->getDomains() as $domain) { - $output->section(sprintf('Displaying messages for domain %s:', $domain)); $newKeys = array_keys($operation->getNewMessages($domain)); $allKeys = array_keys($operation->getMessages($domain)); - $output->listing(array_merge( + + $list = array_merge( array_diff($allKeys, $newKeys), array_map(function ($id) { return sprintf('%s', $id); @@ -165,12 +169,21 @@ protected function execute(InputInterface $input, OutputInterface $output) array_map(function ($id) { return sprintf('%s', $id); }, array_keys($operation->getObsoleteMessages($domain))) - )); + ); + + $domainMessagesCount = count($list); + + $io->section(sprintf('Messages extracted for domain "%s" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : '')); + $io->listing($list); + + $extractedMessagesCount += $domainMessagesCount; } if ('xlf' == $input->getOption('output-format')) { - $output->writeln('Xliff output version is 1.2'); + $io->comment('Xliff output version is 1.2'); } + + $resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was'); } if (true === $input->getOption('no-backup')) { @@ -179,7 +192,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // save the files if (true === $input->getOption('force')) { - $output->text('Writing files'); + $io->comment('Writing files...'); $bundleTransPath = false; foreach ($transPaths as $path) { @@ -194,9 +207,12 @@ protected function execute(InputInterface $input, OutputInterface $output) } $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale'))); + + if (true === $input->getOption('dump-messages')) { + $resultMessage .= ' and translation files were updated'; + } } - $output->newLine(); - $output->success('Success'); + $io->success($resultMessage.'.'); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php index ca39257462dd3..ba77eefad5f5b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Finder\Finder; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser; @@ -62,8 +63,9 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); if (false !== strpos($input->getFirstArgument(), ':l')) { - $output->writeln('The use of "yaml:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:yaml" instead.'); + $io->caution('The use of "yaml:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:yaml" instead.'); } $filename = $input->getArgument('filename'); @@ -78,7 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $content .= fread(STDIN, 1024); } - return $this->display($input, $output, array($this->validate($content))); + return $this->display($input, $output, $io, array($this->validate($content))); } if (0 !== strpos($filename, '@') && !is_readable($filename)) { @@ -99,7 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $filesInfo[] = $this->validate(file_get_contents($file), $file); } - return $this->display($input, $output, $filesInfo); + return $this->display($input, $output, $io, $filesInfo); } private function validate($content, $file = null) @@ -114,33 +116,37 @@ private function validate($content, $file = null) return array('file' => $file, 'valid' => true); } - private function display(InputInterface $input, OutputInterface $output, $files) + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files) { switch ($input->getOption('format')) { case 'txt': - return $this->displayTxt($output, $files); + return $this->displayTxt($output, $io, $files); case 'json': - return $this->displayJson($output, $files); + return $this->displayJson($io, $files); default: throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); } } - private function displayTxt(OutputInterface $output, $filesInfo) + private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInfo) { $errors = 0; foreach ($filesInfo as $info) { if ($info['valid'] && $output->isVerbose()) { - $output->writeln('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$errors; - $output->writeln(sprintf('KO in %s', $info['file'])); - $output->writeln(sprintf('>> %s', $info['message'])); + $io->text(sprintf(' ERROR in %s', $info['file'])); + $io->text(sprintf(' >> %s', $info['message'])); } } - $output->writeln(sprintf('%d/%d valid files', count($filesInfo) - $errors, count($filesInfo))); + if (0 === $errors) { + $io->success(sprintf('All %d YAML files contain valid syntax.', count($filesInfo))); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.', count($filesInfo) - $errors, $errors)); + } return min($errors, 1); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 53d2b1370b886..ff5432455e58e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -72,6 +72,8 @@ public function doRun(InputInterface $input, OutputInterface $output) $this->setDispatcher($container->get('event_dispatcher')); if (true === $input->hasParameterOption(array('--shell', '-s'))) { + @trigger_error('The "--shell" option is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + $shell = new Shell($this); $shell->setProcessIsolation($input->hasParameterOption(array('--process-isolation'))); $shell->run(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index e50a2fc0b20ba..9163c83c5ea2c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -208,19 +208,22 @@ private function getContainerDefinitionData(Definition $definition, $omitTags = { $data = array( 'class' => (string) $definition->getClass(), - 'scope' => $definition->getScope(), + 'scope' => $definition->getScope(false), 'public' => $definition->isPublic(), 'synthetic' => $definition->isSynthetic(), 'lazy' => $definition->isLazy(), + 'shared' => $definition->isShared(), + 'synchronized' => $definition->isSynchronized(false), + 'abstract' => $definition->isAbstract(), + 'autowire' => $definition->isAutowired(), + 'autowiring_types' => array(), + 'file' => $definition->getFile(), ); - if (method_exists($definition, 'isSynchronized')) { - $data['synchronized'] = $definition->isSynchronized(false); + foreach ($definition->getAutowiringTypes() as $autowiringType) { + $data['autowiring_types'][] = $autowiringType; } - $data['abstract'] = $definition->isAbstract(); - $data['file'] = $definition->getFile(); - if ($definition->getFactoryClass(false)) { $data['factory_class'] = $definition->getFactoryClass(false); } @@ -250,11 +253,9 @@ private function getContainerDefinitionData(Definition $definition, $omitTags = if (!$omitTags) { $data['tags'] = array(); - if (count($definition->getTags())) { - foreach ($definition->getTags() as $tagName => $tagData) { - foreach ($tagData as $parameters) { - $data['tags'][] = array('name' => $tagName, 'parameters' => $parameters); - } + foreach ($definition->getTags() as $tagName => $tagData) { + foreach ($tagData as $parameters) { + $data['tags'][] = array('name' => $tagName, 'parameters' => $parameters); } } } @@ -286,14 +287,18 @@ private function getEventDispatcherListenersData(EventDispatcherInterface $event $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { foreach ($registeredListeners as $listener) { - $data[] = $this->getCallableData($listener); + $l = $this->getCallableData($listener); + $l['priority'] = $eventDispatcher->getListenerPriority($event, $listener); + $data[] = $l; } } else { ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { foreach ($eventListeners as $eventListener) { - $data[$eventListened][] = $this->getCallableData($eventListener); + $l = $this->getCallableData($eventListener); + $l['priority'] = $eventDispatcher->getListenerPriority($eventListened, $eventListener); + $data[$eventListened][] = $l; } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index fd522005af0d0..d7ae531ba218e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -179,18 +179,20 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o protected function describeContainerDefinition(Definition $definition, array $options = array()) { $output = '- Class: `'.$definition->getClass().'`' - ."\n".'- Scope: `'.$definition->getScope().'`' + ."\n".'- Scope: `'.$definition->getScope(false).'`' ."\n".'- Public: '.($definition->isPublic() ? 'yes' : 'no') ."\n".'- Synthetic: '.($definition->isSynthetic() ? 'yes' : 'no') ."\n".'- Lazy: '.($definition->isLazy() ? 'yes' : 'no') + ."\n".'- Shared: '.($definition->isShared() ? 'yes' : 'no') + ."\n".'- Synchronized: '.($definition->isSynchronized(false) ? 'yes' : 'no') + ."\n".'- Abstract: '.($definition->isAbstract() ? 'yes' : 'no') + ."\n".'- Autowired: '.($definition->isAutowired() ? 'yes' : 'no') ; - if (method_exists($definition, 'isSynchronized')) { - $output .= "\n".'- Synchronized: '.($definition->isSynchronized(false) ? 'yes' : 'no'); + foreach ($definition->getAutowiringTypes() as $autowiringType) { + $output .= "\n".'- Autowiring Type: `'.$autowiringType.'`'; } - $output .= "\n".'- Abstract: '.($definition->isAbstract() ? 'yes' : 'no'); - if ($definition->getFile()) { $output .= "\n".'- File: `'.$definition->getFile().'`'; } @@ -274,6 +276,7 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev foreach ($registeredListeners as $order => $listener) { $this->write("\n".sprintf('## Listener %d', $order + 1)."\n"); $this->describeCallable($listener); + $this->write(sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($event, $listener))."\n"); } } else { ksort($registeredListeners); @@ -284,6 +287,7 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev foreach ($eventListeners as $order => $eventListener) { $this->write("\n".sprintf('### Listener %d', $order + 1)."\n"); $this->describeCallable($eventListener); + $this->write(sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($eventListened, $eventListener))."\n"); } } } @@ -355,7 +359,7 @@ protected function describeCallable($callable, array $options = array()) */ private function formatRouterConfig(array $array) { - if (!count($array)) { + if (!$array) { return 'NONE'; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 388c3e70845cf..c9b21f032b405 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -34,11 +35,13 @@ class TextDescriptor extends Descriptor protected function describeRouteCollection(RouteCollection $routes, array $options = array()) { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; - $headers = array('Name', 'Method', 'Scheme', 'Host', 'Path'); - $table = new Table($this->getOutput()); - $table->setStyle('compact'); - $table->setHeaders($showControllers ? array_merge($headers, array('Controller')) : $headers); + $tableHeaders = array('Name', 'Method', 'Scheme', 'Host', 'Path'); + if ($showControllers) { + $tableHeaders[] = 'Controller'; + } + + $tableRows = array(); foreach ($routes->all() as $name => $route) { $row = array( $name, @@ -58,11 +61,16 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio $row[] = $controller; } - $table->addRow($row); + $tableRows[] = $row; } - $this->writeText($this->formatSection('router', 'Current routes')."\n", $options); - $table->render(); + if (isset($options['output'])) { + $options['output']->table($tableHeaders, $tableRows); + } else { + $table = new Table($this->getOutput()); + $table->setHeaders($tableHeaders)->setRows($tableRows); + $table->render(); + } } /** @@ -73,26 +81,24 @@ protected function describeRoute(Route $route, array $options = array()) $requirements = $route->getRequirements(); unset($requirements['_scheme'], $requirements['_method']); - // fixme: values were originally written as raw - $description = array( - 'Path '.$route->getPath(), - 'Path Regex '.$route->compile()->getRegex(), - 'Host '.('' !== $route->getHost() ? $route->getHost() : 'ANY'), - 'Host Regex '.('' !== $route->getHost() ? $route->compile()->getHostRegex() : ''), - 'Scheme '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'), - 'Method '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'), - 'Class '.get_class($route), - 'Defaults '.$this->formatRouterConfig($route->getDefaults()), - 'Requirements '.($requirements ? $this->formatRouterConfig($requirements) : 'NO CUSTOM'), - 'Options '.$this->formatRouterConfig($route->getOptions()), + $tableHeaders = array('Property', 'Value'); + $tableRows = array( + array('Route Name', isset($options['name']) ? $options['name'] : ''), + array('Path', $route->getPath()), + array('Path Regex', $route->compile()->getRegex()), + array('Host', ('' !== $route->getHost() ? $route->getHost() : 'ANY')), + array('Host Regex', ('' !== $route->getHost() ? $route->compile()->getHostRegex() : '')), + array('Scheme', ($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY')), + array('Method', ($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')), + array('Requirements', ($requirements ? $this->formatRouterConfig($requirements) : 'NO CUSTOM')), + array('Class', get_class($route)), + array('Defaults', $this->formatRouterConfig($route->getDefaults())), + array('Options', $this->formatRouterConfig($route->getOptions())), ); - if (isset($options['name'])) { - array_unshift($description, 'Name '.$options['name']); - array_unshift($description, $this->formatSection('router', sprintf('Route "%s"', $options['name']))); - } - - $this->writeText(implode("\n", $description)."\n", $options); + $table = new Table($this->getOutput()); + $table->setHeaders($tableHeaders)->setRows($tableRows); + $table->render(); } /** @@ -100,16 +106,15 @@ protected function describeRoute(Route $route, array $options = array()) */ protected function describeContainerParameters(ParameterBag $parameters, array $options = array()) { - $table = new Table($this->getOutput()); - $table->setStyle('compact'); - $table->setHeaders(array('Parameter', 'Value')); + $tableHeaders = array('Parameter', 'Value'); + $tableRows = array(); foreach ($this->sortParameters($parameters) as $parameter => $value) { - $table->addRow(array($parameter, $this->formatParameter($value))); + $tableRows[] = array($parameter, $this->formatParameter($value)); } - $this->writeText($this->formatSection('container', 'List of parameters')."\n", $options); - $table->render(); + $options['output']->title('Symfony Container Parameters'); + $options['output']->table($tableHeaders, $tableRows); } /** @@ -118,15 +123,17 @@ protected function describeContainerParameters(ParameterBag $parameters, array $ protected function describeContainerTags(ContainerBuilder $builder, array $options = array()) { $showPrivate = isset($options['show_private']) && $options['show_private']; - $description = array($this->formatSection('container', 'Tagged services')); - foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { - $description[] = $this->formatSection('tag', $tag); - $description = array_merge($description, array_keys($definitions)); - $description[] = ''; + if ($showPrivate) { + $options['output']->title('Symfony Container Public and Private Tags'); + } else { + $options['output']->title('Symfony Container Public Tags'); } - $this->writeText(implode("\n", $description), $options); + foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + $options['output']->section(sprintf('"%s" tag', $tag)); + $options['output']->listing(array_keys($definitions)); + } } /** @@ -143,11 +150,13 @@ protected function describeContainerService($service, array $options = array()) } elseif ($service instanceof Definition) { $this->describeContainerDefinition($service, $options); } else { - $description = $this->formatSection('container', sprintf('Information for service %s', $options['id'])) - ."\n".sprintf('Service Id %s', isset($options['id']) ? $options['id'] : '-') - ."\n".sprintf('Class %s', get_class($service)); - - $this->writeText($description, $options); + $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); + $options['output']->table( + array('Service ID', 'Class'), + array( + array(isset($options['id']) ? $options['id'] : '-', get_class($service)), + ) + ); } } @@ -160,16 +169,16 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $showTag = isset($options['tag']) ? $options['tag'] : null; if ($showPrivate) { - $label = 'Public and private services'; + $title = 'Symfony Container Public and Private Services'; } else { - $label = 'Public services'; + $title = 'Symfony Container Public Services'; } if ($showTag) { - $label .= ' with tag '.$options['tag'].''; + $title .= sprintf(' Tagged with "%s" Tag', $options['tag']); } - $this->writeText($this->formatSection('container', $label)."\n", $options); + $options['output']->title($title); $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds(); $maxTags = array(); @@ -201,10 +210,8 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $tagsCount = count($maxTags); $tagsNames = array_keys($maxTags); - $table = new Table($this->getOutput()); - $table->setStyle('compact'); - $table->setHeaders(array_merge(array('Service ID'), $tagsNames, array('Class name'))); - + $tableHeaders = array_merge(array('Service ID'), $tagsNames, array('Class name')); + $tableRows = array(); foreach ($this->sortServiceIds($serviceIds) as $serviceId) { $definition = $this->resolveServiceDefinition($builder, $serviceId); if ($definition instanceof Definition) { @@ -215,23 +222,23 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : ''; } if (0 === $key) { - $table->addRow(array_merge(array($serviceId), $tagValues, array($definition->getClass()))); + $tableRows[] = array_merge(array($serviceId), $tagValues, array($definition->getClass())); } else { - $table->addRow(array_merge(array(' "'), $tagValues, array(''))); + $tableRows[] = array_merge(array(' "'), $tagValues, array('')); } } } else { - $table->addRow(array($serviceId, $definition->getClass())); + $tableRows[] = array($serviceId, $definition->getClass()); } } elseif ($definition instanceof Alias) { $alias = $definition; - $table->addRow(array_merge(array($serviceId, sprintf('alias for "%s"', $alias)), $tagsCount ? array_fill(0, $tagsCount, '') : array())); + $tableRows[] = array_merge(array($serviceId, sprintf('alias for "%s"', $alias)), $tagsCount ? array_fill(0, $tagsCount, '') : array()); } else { - $table->addRow(array_merge(array($serviceId, get_class($definition)), $tagsCount ? array_fill(0, $tagsCount, '') : array())); + $tableRows[] = array_merge(array($serviceId, get_class($definition)), $tagsCount ? array_fill(0, $tagsCount, '') : array()); } } - $table->render(); + $options['output']->table($tableHeaders, $tableRows); } /** @@ -239,68 +246,79 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o */ protected function describeContainerDefinition(Definition $definition, array $options = array()) { - $description = isset($options['id']) - ? array($this->formatSection('container', sprintf('Information for service %s', $options['id']))) - : array(); + if (isset($options['id'])) { + $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); + } + + $tableHeaders = array('Option', 'Value'); - $description[] = sprintf('Service Id %s', isset($options['id']) ? $options['id'] : '-'); - $description[] = sprintf('Class %s', $definition->getClass() ?: '-'); + $tableRows[] = array('Service ID', isset($options['id']) ? $options['id'] : '-'); + $tableRows[] = array('Class', $definition->getClass() ?: '-'); - $tags = $definition->getTags(); - if (count($tags)) { - $description[] = 'Tags'; + if ($tags = $definition->getTags()) { + $tagInformation = ''; foreach ($tags as $tagName => $tagData) { - foreach ($tagData as $parameters) { - $description[] = sprintf(' - %-30s (%s)', $tagName, implode(', ', array_map(function ($key, $value) { + foreach ($tagData as $tagParameters) { + $parameters = array_map(function ($key, $value) { return sprintf('%s: %s', $key, $value); - }, array_keys($parameters), array_values($parameters)))); + }, array_keys($tagParameters), array_values($tagParameters)); + $parameters = implode(', ', $parameters); + + if ('' === $parameters) { + $tagInformation .= sprintf('%s', $tagName); + } else { + $tagInformation .= sprintf('%s (%s)', $tagName, $parameters); + } } } } else { - $description[] = 'Tags -'; + $tagInformation = '-'; } + $tableRows[] = array('Tags', $tagInformation); - $description[] = sprintf('Scope %s', $definition->getScope()); - $description[] = sprintf('Public %s', $definition->isPublic() ? 'yes' : 'no'); - $description[] = sprintf('Synthetic %s', $definition->isSynthetic() ? 'yes' : 'no'); - $description[] = sprintf('Lazy %s', $definition->isLazy() ? 'yes' : 'no'); - if (method_exists($definition, 'isSynchronized')) { - $description[] = sprintf('Synchronized %s', $definition->isSynchronized(false) ? 'yes' : 'no'); - } - $description[] = sprintf('Abstract %s', $definition->isAbstract() ? 'yes' : 'no'); + $tableRows[] = array('Scope', $definition->getScope(false)); + $tableRows[] = array('Public', $definition->isPublic() ? 'yes' : 'no'); + $tableRows[] = array('Synthetic', $definition->isSynthetic() ? 'yes' : 'no'); + $tableRows[] = array('Lazy', $definition->isLazy() ? 'yes' : 'no'); + $tableRows[] = array('Synchronized', $definition->isSynchronized(false) ? 'yes' : 'no'); + $tableRows[] = array('Abstract', $definition->isAbstract() ? 'yes' : 'no'); + $tableRows[] = array('Autowired', $definition->isAutowired() ? 'yes' : 'no'); + + $autowiringTypes = $definition->getAutowiringTypes(); + $tableRows[] = array('Autowiring Types', $autowiringTypes ? implode(', ', $autowiringTypes) : '-'); if ($definition->getFile()) { - $description[] = sprintf('Required File %s', $definition->getFile() ?: '-'); + $tableRows[] = array('Required File', $definition->getFile() ? $definition->getFile() : '-'); } if ($definition->getFactoryClass(false)) { - $description[] = sprintf('Factory Class %s', $definition->getFactoryClass(false)); + $tableRows[] = array('Factory Class', $definition->getFactoryClass(false)); } if ($definition->getFactoryService(false)) { - $description[] = sprintf('Factory Service %s', $definition->getFactoryService(false)); + $tableRows[] = array('Factory Service', $definition->getFactoryService(false)); } if ($definition->getFactoryMethod(false)) { - $description[] = sprintf('Factory Method %s', $definition->getFactoryMethod(false)); + $tableRows[] = array('Factory Method', $definition->getFactoryMethod(false)); } if ($factory = $definition->getFactory()) { if (is_array($factory)) { if ($factory[0] instanceof Reference) { - $description[] = sprintf('Factory Service %s', $factory[0]); + $tableRows[] = array('Factory Service', $factory[0]); } elseif ($factory[0] instanceof Definition) { throw new \InvalidArgumentException('Factory is not describable.'); } else { - $description[] = sprintf('Factory Class %s', $factory[0]); + $tableRows[] = array('Factory Class', $factory[0]); } - $description[] = sprintf('Factory Method %s', $factory[1]); + $tableRows[] = array('Factory Method', $factory[1]); } else { - $description[] = sprintf('Factory Function %s', $factory); + $tableRows[] = array('Factory Function', $factory); } } - $this->writeText(implode("\n", $description)."\n", $options); + $options['output']->table($tableHeaders, $tableRows); } /** @@ -308,7 +326,7 @@ protected function describeContainerDefinition(Definition $definition, array $op */ protected function describeContainerAlias(Alias $alias, array $options = array()) { - $this->writeText(sprintf("This service is an alias for the service %s\n", (string) $alias), $options); + $options['output']->comment(sprintf('This service is an alias for the service %s', (string) $alias)); } /** @@ -316,7 +334,12 @@ protected function describeContainerAlias(Alias $alias, array $options = array() */ protected function describeContainerParameter($parameter, array $options = array()) { - $this->writeText($this->formatParameter($parameter), $options); + $options['output']->table( + array('Parameter', 'Value'), + array( + array($options['parameter'], $this->formatParameter($parameter), + ), + )); } /** @@ -326,42 +349,22 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev { $event = array_key_exists('event', $options) ? $options['event'] : null; - $label = 'Registered listeners'; if (null !== $event) { - $label .= sprintf(' for event %s', $event); + $title = sprintf('Registered Listeners for "%s" Event', $event); } else { - $label .= ' by event'; + $title = 'Registered Listeners Grouped by Event'; } - $this->writeText($this->formatSection('event_dispatcher', $label)."\n", $options); + $options['output']->title($title); $registeredListeners = $eventDispatcher->getListeners($event); - if (null !== $event) { - $this->writeText("\n"); - $table = new Table($this->getOutput()); - $table->getStyle()->setCellHeaderFormat('%s'); - $table->setHeaders(array('Order', 'Callable')); - - foreach ($registeredListeners as $order => $listener) { - $table->addRow(array(sprintf('#%d', $order + 1), $this->formatCallable($listener))); - } - - $table->render(); + $this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']); } else { ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { - $this->writeText(sprintf("\n[Event] %s\n", $eventListened), $options); - - $table = new Table($this->getOutput()); - $table->getStyle()->setCellHeaderFormat('%s'); - $table->setHeaders(array('Order', 'Callable')); - - foreach ($eventListeners as $order => $eventListener) { - $table->addRow(array(sprintf('#%d', $order + 1), $this->formatCallable($eventListener))); - } - - $table->render(); + $options['output']->section(sprintf('"%s" event', $eventListened)); + $this->renderEventListenerTable($eventDispatcher, $eventListened, $eventListeners, $options['output']); } } } @@ -374,33 +377,38 @@ protected function describeCallable($callable, array $options = array()) $this->writeText($this->formatCallable($callable), $options); } - /** - * @return string - */ - private function formatRouterConfig(array $array) + private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, $event, array $eventListeners, SymfonyStyle $io) { - if (!count($array)) { - return 'NONE'; - } + $tableHeaders = array('Order', 'Callable', 'Priority'); + $tableRows = array(); - $string = ''; - ksort($array); - foreach ($array as $name => $value) { - $string .= ($string ? "\n".str_repeat(' ', 13) : '').$name.': '.$this->formatValue($value); + $order = 1; + foreach ($eventListeners as $order => $listener) { + $tableRows[] = array(sprintf('#%d', $order + 1), $this->formatCallable($listener), $eventDispatcher->getListenerPriority($event, $listener)); } - return $string; + $io->table($tableHeaders, $tableRows); } /** - * @param string $section - * @param string $message + * @param array $config * * @return string */ - private function formatSection($section, $message) + private function formatRouterConfig(array $config) { - return sprintf('[%s] %s', $section, $message); + if (empty($config)) { + return 'NONE'; + } + + ksort($config); + + $configAsString = ''; + foreach ($config as $key => $value) { + $configAsString .= sprintf("\n%s: %s", $key, $this->formatValue($value)); + } + + return trim($configAsString); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index e2bf073f6df3c..66b4f02041a5c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -183,7 +183,7 @@ private function getRouteDocument(Route $route, $name = null) $methodXML->appendChild(new \DOMText($method)); } - if (count($route->getDefaults())) { + if ($route->getDefaults()) { $routeXML->appendChild($defaultsXML = $dom->createElement('defaults')); foreach ($route->getDefaults() as $attribute => $value) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); @@ -194,7 +194,7 @@ private function getRouteDocument(Route $route, $name = null) $requirements = $route->getRequirements(); unset($requirements['_scheme'], $requirements['_method']); - if (count($requirements)) { + if ($requirements) { $routeXML->appendChild($requirementsXML = $dom->createElement('requirements')); foreach ($requirements as $attribute => $pattern) { $requirementsXML->appendChild($requirementXML = $dom->createElement('requirement')); @@ -203,7 +203,7 @@ private function getRouteDocument(Route $route, $name = null) } } - if (count($route->getOptions())) { + if ($route->getOptions()) { $routeXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($route->getOptions() as $name => $value) { $optionsXML->appendChild($optionXML = $dom->createElement('option')); @@ -325,18 +325,16 @@ private function getContainerDefinitionDocument(Definition $definition, $id = nu $serviceXML->setAttribute('class', $definition->getClass()); - if (method_exists($definition, 'getFactoryMethod')) { - if ($definition->getFactoryClass(false)) { - $serviceXML->setAttribute('factory-class', $definition->getFactoryClass(false)); - } + if ($definition->getFactoryClass(false)) { + $serviceXML->setAttribute('factory-class', $definition->getFactoryClass(false)); + } - if ($definition->getFactoryService(false)) { - $serviceXML->setAttribute('factory-service', $definition->getFactoryService(false)); - } + if ($definition->getFactoryService(false)) { + $serviceXML->setAttribute('factory-service', $definition->getFactoryService(false)); + } - if ($definition->getFactoryMethod(false)) { - $serviceXML->setAttribute('factory-method', $definition->getFactoryMethod(false)); - } + if ($definition->getFactoryMethod(false)) { + $serviceXML->setAttribute('factory-method', $definition->getFactoryMethod(false)); } if ($factory = $definition->getFactory()) { @@ -356,20 +354,18 @@ private function getContainerDefinitionDocument(Definition $definition, $id = nu } } - $serviceXML->setAttribute('scope', $definition->getScope()); + $serviceXML->setAttribute('scope', $definition->getScope(false)); $serviceXML->setAttribute('public', $definition->isPublic() ? 'true' : 'false'); $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false'); $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false'); - if (method_exists($definition, 'isSynchronized')) { - $serviceXML->setAttribute('synchronized', $definition->isSynchronized(false) ? 'true' : 'false'); - } + $serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false'); + $serviceXML->setAttribute('synchronized', $definition->isSynchronized(false) ? 'true' : 'false'); $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false'); + $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false'); $serviceXML->setAttribute('file', $definition->getFile()); if (!$omitTags) { - $tags = $definition->getTags(); - - if (count($tags) > 0) { + if ($tags = $definition->getTags()) { $serviceXML->appendChild($tagsXML = $dom->createElement('tags')); foreach ($tags as $tagName => $tagData) { foreach ($tagData as $parameters) { @@ -439,11 +435,7 @@ private function getEventDispatcherListenersDocument(EventDispatcherInterface $e $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { - foreach ($registeredListeners as $listener) { - $callableXML = $this->getCallableDocument($listener); - - $eventDispatcherXML->appendChild($eventDispatcherXML->ownerDocument->importNode($callableXML->childNodes->item(0), true)); - } + $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners); } else { ksort($registeredListeners); @@ -451,17 +443,27 @@ private function getEventDispatcherListenersDocument(EventDispatcherInterface $e $eventDispatcherXML->appendChild($eventXML = $dom->createElement('event')); $eventXML->setAttribute('name', $eventListened); - foreach ($eventListeners as $eventListener) { - $callableXML = $this->getCallableDocument($eventListener); - - $eventXML->appendChild($eventXML->ownerDocument->importNode($callableXML->childNodes->item(0), true)); - } + $this->appendEventListenerDocument($eventDispatcher, $eventListened, $eventXML, $eventListeners); } } return $dom; } + /** + * @param \DOMElement $element + * @param array $eventListeners + */ + private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, $event, \DOMElement $element, array $eventListeners) + { + foreach ($eventListeners as $listener) { + $callableXML = $this->getCallableDocument($listener); + $callableXML->childNodes->item(0)->setAttribute('priority', $eventDispatcher->getListenerPriority($event, $listener)); + + $element->appendChild($element->ownerDocument->importNode($callableXML->childNodes->item(0), true)); + } + } + /** * @param callable $callable * diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php b/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php index 80daebd4cd3b0..8a1e407ed253b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php @@ -16,6 +16,8 @@ /** * Shell. * + * @deprecated since version 2.8, to be removed in 3.0. + * * @author Fabien Potencier */ class Shell extends BaseShell diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php index e04d367c85487..66e80d05e5222 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php @@ -39,9 +39,9 @@ class Controller extends ContainerAware /** * Generates a URL from the given parameters. * - * @param string $route The name of the route - * @param mixed $parameters An array of parameters - * @param bool|string $referenceType The type of reference (one of the constants in UrlGeneratorInterface) + * @param string $route The name of the route + * @param mixed $parameters An array of parameters + * @param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface) * * @return string The generated URL * @@ -159,7 +159,15 @@ protected function denyAccessUnlessGranted($attributes, $object = null, $message */ public function renderView($view, array $parameters = array()) { - return $this->container->get('templating')->render($view, $parameters); + if ($this->container->has('templating')) { + return $this->container->get('templating')->render($view, $parameters); + } + + if (!$this->container->has('twig')) { + throw new \LogicException('You can not use the "renderView" method if the Templating Component or the Twig Bundle are not available.'); + } + + return $this->container->get('twig')->render($view, $parameters); } /** @@ -173,7 +181,21 @@ public function renderView($view, array $parameters = array()) */ public function render($view, array $parameters = array(), Response $response = null) { - return $this->container->get('templating')->renderResponse($view, $parameters, $response); + if ($this->container->has('templating')) { + return $this->container->get('templating')->renderResponse($view, $parameters, $response); + } + + if (!$this->container->has('twig')) { + throw new \LogicException('You can not use the "render" method if the Templating Component or the Twig Bundle are not available.'); + } + + if (null === $response) { + $response = new Response(); + } + + $response->setContent($this->container->get('twig')->render($view, $parameters)); + + return $response; } /** @@ -187,11 +209,21 @@ public function render($view, array $parameters = array(), Response $response = */ public function stream($view, array $parameters = array(), StreamedResponse $response = null) { - $templating = $this->container->get('templating'); - - $callback = function () use ($templating, $view, $parameters) { - $templating->stream($view, $parameters); - }; + if ($this->container->has('templating')) { + $templating = $this->container->get('templating'); + + $callback = function () use ($templating, $view, $parameters) { + $templating->stream($view, $parameters); + }; + } elseif ($this->container->has('twig')) { + $twig = $this->container->get('twig'); + + $callback = function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }; + } else { + throw new \LogicException('You can not use the "stream" method if the Templating Component or the Twig Bundle are not available.'); + } if (null === $response) { return new StreamedResponse($callback); @@ -260,7 +292,16 @@ public function createForm($type, $data = null, array $options = array()) */ public function createFormBuilder($data = null, array $options = array()) { - return $this->container->get('form.factory')->createBuilder('form', $data, $options); + if (method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')) { + $type = 'Symfony\Component\Form\Extension\Core\Type\FormType'; + } else { + // not using the class name is deprecated since Symfony 2.8 and + // is only used for backwards compatibility with older versions + // of the Form component + $type = 'form'; + } + + return $this->container->get('form.factory')->createBuilder($type, $data, $options); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index f75ab2d79514f..c3a2f82fa17e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -33,8 +33,13 @@ class TemplateController extends ContainerAware */ public function templateAction($template, $maxAge = null, $sharedAge = null, $private = null) { - /** @var $response \Symfony\Component\HttpFoundation\Response */ - $response = $this->container->get('templating')->renderResponse($template); + if ($this->container->has('templating')) { + $response = $this->container->get('templating')->renderResponse($template); + } elseif ($this->container->has('twig')) { + $response = new Response($this->container->get('twig')->render($template)); + } else { + throw new \LogicException('You can not use the TemplateController if the Templating Component or the Twig Bundle are not available.'); + } if ($maxAge) { $response->setMaxAge($maxAge); diff --git a/src/Symfony/Bundle/FrameworkBundle/DataCollector/AjaxDataCollector.php b/src/Symfony/Bundle/FrameworkBundle/DataCollector/AjaxDataCollector.php index 0dff2b2a57d44..0e1566b630b3d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DataCollector/AjaxDataCollector.php +++ b/src/Symfony/Bundle/FrameworkBundle/DataCollector/AjaxDataCollector.php @@ -11,24 +11,17 @@ namespace Symfony\Bundle\FrameworkBundle\DataCollector; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector as BaseAjaxDataCollector; + +@trigger_error('The '.__NAMESPACE__.'\AjaxDataCollector class is deprecated since Symfony 2.8 and will be removed in 3.0. Use Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector instead.', E_USER_DEPRECATED); /** * AjaxDataCollector. * * @author Bart van den Burg + * + * @deprecated since version 2.8, to be removed in 3.0. */ -class AjaxDataCollector extends DataCollector +class AjaxDataCollector extends BaseAjaxDataCollector { - public function collect(Request $request, Response $response, \Exception $exception = null) - { - // all collecting is done client side - } - - public function getName() - { - return 'ajax'; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php new file mode 100644 index 0000000000000..a8e1c549a2d6a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority. + * + * @author Matthias Pigulla + * @author Benjamin Klotz + */ +class ConfigCachePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $resourceCheckers = array(); + + foreach ($container->findTaggedServiceIds('config_cache.resource_checker') as $id => $tags) { + $priority = isset($tags[0]['priority']) ? $tags[0]['priority'] : 0; + $resourceCheckers[$priority][] = new Reference($id); + } + + if (empty($resourceCheckers)) { + return; + } + + // sort by priority and flatten + krsort($resourceCheckers); + $resourceCheckers = call_user_func_array('array_merge', $resourceCheckers); + + $container->getDefinition('config_cache_factory')->replaceArgument(0, $resourceCheckers); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php index 8b46b946e550d..e1afde82408c5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php @@ -34,12 +34,21 @@ public function process(ContainerBuilder $container) $types = array(); foreach ($container->findTaggedServiceIds('form.type') as $serviceId => $tag) { - $alias = isset($tag[0]['alias']) - ? $tag[0]['alias'] - : $serviceId; + $serviceDefinition = $container->getDefinition($serviceId); + if (!$serviceDefinition->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as form types are lazy-loaded.', $serviceId)); + } + // The following if-else block is deprecated and will be removed + // in Symfony 3.0 + // Deprecation errors are triggered in the form registry + if (isset($tag[0]['alias'])) { + $types[$tag[0]['alias']] = $serviceId; + } else { + $types[$serviceId] = $serviceId; + } - // Flip, because we want tag aliases (= type identifiers) as keys - $types[$alias] = $serviceId; + // Support type access by FQCN + $types[$serviceDefinition->getClass()] = $serviceId; } $definition->replaceArgument(1, $types); @@ -47,17 +56,34 @@ public function process(ContainerBuilder $container) $typeExtensions = array(); foreach ($container->findTaggedServiceIds('form.type_extension') as $serviceId => $tag) { - $alias = isset($tag[0]['alias']) - ? $tag[0]['alias'] - : $serviceId; + $serviceDefinition = $container->getDefinition($serviceId); + if (!$serviceDefinition->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as form type extensions are lazy-loaded.', $serviceId)); + } - $typeExtensions[$alias][] = $serviceId; + if (isset($tag[0]['extended_type'])) { + $extendedType = $tag[0]['extended_type']; + } elseif (isset($tag[0]['alias'])) { + @trigger_error(sprintf('The alias option of the form.type_extension tag of service "%s" is deprecated since Symfony 2.8 and will be removed in 3.0. Use the extended_type option instead.', $serviceId), E_USER_DEPRECATED); + $extendedType = $tag[0]['alias']; + } else { + @trigger_error(sprintf('The extended_type option of the form.type_extension tag of service "%s" is required since Symfony 2.8.', $serviceId), E_USER_DEPRECATED); + $extendedType = $serviceId; + } + + $typeExtensions[$extendedType][] = $serviceId; } $definition->replaceArgument(2, $typeExtensions); // Find all services annotated with "form.type_guesser" $guessers = array_keys($container->findTaggedServiceIds('form.type_guesser')); + foreach ($guessers as $serviceId) { + $serviceDefinition = $container->getDefinition($serviceId); + if (!$serviceDefinition->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as form type guessers are lazy-loaded.', $serviceId)); + } + } $definition->replaceArgument(3, $guessers); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/PropertyInfoPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/PropertyInfoPass.php new file mode 100644 index 0000000000000..589af52fd69e9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/PropertyInfoPass.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\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds extractors to the property_info service. + * + * @author Kévin Dunglas + */ +class PropertyInfoPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('property_info')) { + return; + } + + $listExtractors = $this->findAndSortTaggedServices('property_info.list_extractor', $container); + $container->getDefinition('property_info')->replaceArgument(0, $listExtractors); + + $typeExtractors = $this->findAndSortTaggedServices('property_info.type_extractor', $container); + $container->getDefinition('property_info')->replaceArgument(1, $typeExtractors); + + $descriptionExtractors = $this->findAndSortTaggedServices('property_info.description_extractor', $container); + $container->getDefinition('property_info')->replaceArgument(2, $descriptionExtractors); + + $accessExtractors = $this->findAndSortTaggedServices('property_info.access_extractor', $container); + $container->getDefinition('property_info')->replaceArgument(3, $accessExtractors); + } + + /** + * Finds all services with the given tag name and order them by their priority. + * + * @param string $tagName + * @param ContainerBuilder $container + * + * @return array + */ + private function findAndSortTaggedServices($tagName, ContainerBuilder $container) + { + $services = $container->findTaggedServiceIds($tagName); + + $sortedServices = array(); + foreach ($services as $serviceId => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $sortedServices[$priority][] = new Reference($serviceId); + } + + if (empty($sortedServices)) { + return array(); + } + + krsort($sortedServices); + + // Flatten the array + return call_user_func_array('array_merge', $sortedServices); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SerializerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SerializerPass.php index 21bc2dac30a64..f9809db25d1dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SerializerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SerializerPass.php @@ -38,6 +38,16 @@ public function process(ContainerBuilder $container) $container->getDefinition('serializer')->replaceArgument(1, $encoders); } + /** + * Finds all services with the given tag name and order them by their priority. + * + * @param string $tagName + * @param ContainerBuilder $container + * + * @return array + * + * @throws \RuntimeException + */ private function findAndSortTaggedServices($tagName, ContainerBuilder $container) { $services = $container->findTaggedServiceIds($tagName); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php new file mode 100644 index 0000000000000..ed852fd041942 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Find all service tags which are defined, but not used and yield a warning log message. + * + * @author Florian Pfitzer + */ +class UnusedTagsPass implements CompilerPassInterface +{ + private $whitelist = array( + 'console.command', + 'config_cache.resource_checker', + 'data_collector', + 'form.type', + 'form.type_extension', + 'form.type_guesser', + 'kernel.cache_clearer', + 'kernel.cache_warmer', + 'kernel.event_listener', + 'kernel.event_subscriber', + 'kernel.fragment_renderer', + 'monolog.logger', + 'routing.expression_language_provider', + 'routing.loader', + 'security.expression_language_provider', + 'security.remember_me_aware', + 'security.voter', + 'serializer.encoder', + 'serializer.normalizer', + 'templating.helper', + 'translation.dumper', + 'translation.extractor', + 'translation.loader', + 'twig.extension', + 'twig.loader', + 'validator.constraint_validator', + 'validator.initializer', + ); + + public function process(ContainerBuilder $container) + { + $compiler = $container->getCompiler(); + $formatter = $compiler->getLoggingFormatter(); + $tags = array_unique(array_merge($container->findTags(), $this->whitelist)); + + foreach ($container->findUnusedTags() as $tag) { + // skip whitelisted tags + if (in_array($tag, $this->whitelist)) { + continue; + } + + // check for typos + $candidates = array(); + foreach ($tags as $definedTag) { + if ($definedTag === $tag) { + continue; + } + + if (false !== strpos($definedTag, $tag) || levenshtein($tag, $definedTag) <= strlen($tag) / 3) { + $candidates[] = $definedTag; + } + } + + $services = array_keys($container->findTaggedServiceIds($tag)); + $message = sprintf('Tag "%s" was defined on service(s) "%s", but was never used.', $tag, implode('", "', $services)); + if (!empty($candidates)) { + $message .= sprintf(' Did you mean "%s"?', implode('", "', $candidates)); + } + + $compiler->addLogMessage($formatter->format($this, $message)); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 3c4ffb2773a76..1bda5d65c9aea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -182,6 +182,7 @@ public function getConfigTreeBuilder() $this->addAnnotationsSection($rootNode); $this->addSerializerSection($rootNode); $this->addPropertyAccessSection($rootNode); + $this->addPropertyInfoSection($rootNode); return $treeBuilder; } @@ -276,10 +277,50 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode) ->booleanNode('collect')->defaultTrue()->end() ->booleanNode('only_exceptions')->defaultFalse()->end() ->booleanNode('only_master_requests')->defaultFalse()->end() - ->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end() - ->scalarNode('username')->defaultValue('')->end() - ->scalarNode('password')->defaultValue('')->end() - ->scalarNode('lifetime')->defaultValue(86400)->end() + ->scalarNode('dsn') + ->defaultValue('file:%kernel.cache_dir%/profiler') + ->beforeNormalization() + ->ifTrue(function ($v) { return 'file:' !== substr($v, 0, 5); }) + ->then(function ($v) { + @trigger_error('The profiler.dsn configuration key must start with "file:" because all the storages except the filesystem are deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return $v; + }) + ->end() + ->end() + ->scalarNode('username') + ->defaultValue('') + ->beforeNormalization() + ->always() + ->then(function ($v) { + @trigger_error('The profiler.username configuration key is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return $v; + }) + ->end() + ->end() + ->scalarNode('password') + ->defaultValue('') + ->beforeNormalization() + ->always() + ->then(function ($v) { + @trigger_error('The profiler.password configuration key is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return $v; + }) + ->end() + ->end() + ->scalarNode('lifetime') + ->defaultValue(86400) + ->beforeNormalization() + ->always() + ->then(function ($v) { + @trigger_error('The profiler.lifetime configuration key is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return $v; + }) + ->end() + ->end() ->arrayNode('matcher') ->canBeUnset() ->performNoDeepMerging() @@ -353,7 +394,8 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->scalarNode('cookie_path')->end() ->scalarNode('cookie_domain')->end() ->booleanNode('cookie_secure')->end() - ->booleanNode('cookie_httponly')->end() + ->booleanNode('cookie_httponly')->defaultTrue()->end() + ->booleanNode('use_cookies')->end() ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() @@ -600,6 +642,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->info('translator configuration') ->canBeEnabled() ->fixXmlConfig('fallback') + ->fixXmlConfig('path') ->children() ->arrayNode('fallbacks') ->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end() @@ -607,6 +650,9 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->defaultValue(array('en')) ->end() ->booleanNode('logging')->defaultValue($this->debug)->end() + ->arrayNode('paths') + ->prototype('scalar')->end() + ->end() ->end() ->end() ->end() @@ -624,7 +670,15 @@ private function addValidationSection(ArrayNodeDefinition $rootNode) ->scalarNode('cache') ->beforeNormalization() // Can be removed in 3.0, once ApcCache support is dropped - ->ifString()->then(function ($v) { return 'apc' === $v ? 'validator.mapping.cache.apc' : $v; }) + ->ifString()->then(function ($v) { + if ('apc' === $v) { + @trigger_error('The ability to pass "apc" as the framework.validation.cache configuration key value is deprecated since Symfony 2.8 and will be removed in 3.0. Use the "validator.mapping.cache.doctrine.apc" service id instead.', E_USER_DEPRECATED); + + return 'validator.mapping.cache.apc'; + } + + return $v; + }) ->end() ->end() ->booleanNode('enable_annotations')->defaultFalse()->end() @@ -681,6 +735,7 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode) ->children() ->booleanNode('enable_annotations')->defaultFalse()->end() ->scalarNode('cache')->end() + ->scalarNode('name_converter')->end() ->end() ->end() ->end() @@ -702,4 +757,16 @@ private function addPropertyAccessSection(ArrayNodeDefinition $rootNode) ->end() ; } + + private function addPropertyInfoSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('property_info') + ->info('Property info configuration') + ->canBeEnabled() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 4eabcc662111b..a20be83dfbced 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -129,6 +129,10 @@ public function load(array $configs, ContainerBuilder $container) $this->registerSerializerConfiguration($config['serializer'], $container, $loader); } + if (isset($config['property_info'])) { + $this->registerPropertyInfoConfiguration($config['property_info'], $container, $loader); + } + $loader->load('debug_prod.xml'); $definition = $container->findDefinition('debug.debug_handlers_listener'); @@ -347,7 +351,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c // session storage $container->setAlias('session.storage', $config['storage_id']); $options = array(); - foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'use_strict_mode') as $key) { + foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'use_strict_mode') as $key) { if (isset($config[$key])) { $options[$key] = $config[$key]; } @@ -609,6 +613,15 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $dirs[] = $dir; } } + + foreach ($config['paths'] as $dir) { + if (is_dir($dir)) { + $dirs[] = $dir; + } else { + throw new \UnexpectedValueException(sprintf('%s defined in translator.paths does not exist or is not a directory', $dir)); + } + } + if (is_dir($dir = $rootDir.'/Resources/translations')) { $dirs[] = $dir; } @@ -741,22 +754,29 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde { $loader->load('annotations.xml'); - if ('file' === $config['cache']) { - $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); - if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { - throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir)); + if ('none' !== $config['cache']) { + if ('file' === $config['cache']) { + $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); + if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { + throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir)); + } + + $container + ->getDefinition('annotations.filesystem_cache') + ->replaceArgument(0, $cacheDir) + ; + + // The annotations.file_cache_reader service is deprecated + $container + ->getDefinition('annotations.file_cache_reader') + ->replaceArgument(1, $cacheDir) + ->replaceArgument(2, $config['debug']) + ; } - $container - ->getDefinition('annotations.file_cache_reader') - ->replaceArgument(1, $cacheDir) - ->replaceArgument(2, $config['debug']) - ; - $container->setAlias('annotation_reader', 'annotations.file_cache_reader'); - } elseif ('none' !== $config['cache']) { $container ->getDefinition('annotations.cached_reader') - ->replaceArgument(1, new Reference($config['cache'])) + ->replaceArgument(1, new Reference('file' !== $config['cache'] ? $config['cache'] : 'annotations.filesystem_cache')) ->replaceArgument(2, $config['debug']) ; $container->setAlias('annotation_reader', 'annotations.cached_reader'); @@ -866,6 +886,32 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder 1, new Reference($config['cache']) ); } + + if (isset($config['name_converter']) && $config['name_converter']) { + $container->getDefinition('serializer.normalizer.object')->replaceArgument(1, new Reference($config['name_converter'])); + } + } + + /** + * Loads property info configuration. + * + * @param array $config + * @param ContainerBuilder $container + * @param XmlFileLoader $loader + */ + private function registerPropertyInfoConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + { + if (!$config['enabled']) { + return; + } + + $loader->load('property_info.xml'); + + if (class_exists('phpDocumentor\Reflection\ClassReflector')) { + $definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor'); + $definition->addTag('property_info.description_extractor', array('priority' => -1000)); + $definition->addTag('property_info.type_extractor', array('priority' => -1001)); + } } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 01f400d7d5e44..3848dabc3b984 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass; @@ -29,6 +30,8 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -89,11 +92,14 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new TranslationDumperPass()); $container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new SerializerPass()); + $container->addCompilerPass(new PropertyInfoPass()); $container->addCompilerPass(new DataCollectorTranslatorPass()); if ($container->getParameter('kernel.debug')) { + $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CompilerDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new ConfigCachePass()); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php new file mode 100644 index 0000000000000..fec5b72a5b3be --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Kernel; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Routing\RouteCollectionBuilder; + +/** + * A Kernel that provides configuration hooks. + * + * @author Ryan Weaver + * @author Fabien Potencier + */ +trait MicroKernelTrait +{ + /** + * Add or import routes into your application. + * + * $routes->import('config/routing.yml'); + * $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + * + * @param RouteCollectionBuilder $routes + */ + abstract protected function configureRoutes(RouteCollectionBuilder $routes); + + /** + * Configures the container. + * + * You can register extensions: + * + * $c->loadFromExtension('framework', array( + * 'secret' => '%secret%' + * )); + * + * Or services: + * + * $c->register('halloween', 'FooBundle\HalloweenProvider'); + * + * Or parameters: + * + * $c->setParameter('halloween', 'lot of fun'); + * + * @param ContainerBuilder $c + * @param LoaderInterface $loader + */ + abstract protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader); + + /** + * {@inheritdoc} + */ + public function registerContainerConfiguration(LoaderInterface $loader) + { + $loader->load(function (ContainerBuilder $container) use ($loader) { + $container->loadFromExtension('framework', array( + 'router' => array( + 'resource' => 'kernel:loadRoutes', + 'type' => 'service', + ), + )); + + $this->configureContainer($container, $loader); + + $container->addObjectResource($this); + }); + } + + /** + * @internal + */ + public function loadRoutes(LoaderInterface $loader) + { + $routes = new RouteCollectionBuilder($loader); + $this->configureRoutes($routes); + + return $routes->build(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml index 1c0c312dc2673..55f0c76fb3d83 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml @@ -19,7 +19,12 @@
+ + + + + The "%service_id%" service is deprecated since 2.8 and will be removed in 3.0. diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml index 4f2e1fbf362a0..25a862e11ff1d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml @@ -21,13 +21,13 @@ - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml index 7e2cff090fc96..33090c5a350c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml @@ -17,47 +17,47 @@ - + - + - - + + - + - + - + - + - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index e2b0df0820b61..7e86fb0728a03 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -129,6 +129,9 @@ + + + @@ -166,7 +169,7 @@ - + @@ -180,17 +183,17 @@ - + - + - + - + %validator.translation_domain% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 5da44c464eb44..83a389dda0ec5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -7,10 +7,11 @@ + The "%service_id%" service is deprecated since Symfony 2.4 and will be removed in 3.0. Use the "security.csrf.token_manager" service instead. - + %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml index 1e8e3c89834d5..6e2849e5bcd11 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml @@ -21,7 +21,7 @@ - + @@ -29,7 +29,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml index d3687da13a5d3..bb484b26a45da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml @@ -16,8 +16,8 @@ - %kernel.debug% + %kernel.debug% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml index cee86b3b72cfa..167deb02fb8fa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml @@ -26,10 +26,10 @@ + %profiler_listener.only_exceptions% %profiler_listener.only_master_requests% - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml new file mode 100644 index 0000000000000..8f6a7c9e31bed --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 5cf865c3d63eb..bbf54e988f390 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -45,10 +45,18 @@ + + + + + + + + + + - - @@ -68,6 +76,9 @@ %router.options.matcher.cache_class% + + + @@ -90,9 +101,9 @@ + - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 3cc6646cdef35..0e45cc55b21df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -34,6 +34,7 @@ + @@ -113,6 +114,7 @@ + @@ -190,6 +192,7 @@ + @@ -225,5 +228,10 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml index 5922d146649af..34400075bb806 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml @@ -10,6 +10,8 @@ - + + The "%service_id%" service is deprecated since Symfony 2.8 and will be removed in 3.0. Use the random_bytes() function instead. + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml index 002a8a4ac2820..4bab610b81c54 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml @@ -11,9 +11,7 @@ - - - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml index e5b53e94938e2..db5cdb8a54c86 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml @@ -22,7 +22,7 @@ - null + null @@ -55,5 +55,8 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 2021505726fef..d7e207c14952a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -34,22 +34,22 @@ + + + + - - + + The "%service_id%" service is deprecated since Symfony 2.7 and will be removed in 3.0. Use the "request_stack" service instead. + - + + Symfony\Component\DependencyInjection\ContainerInterface + Symfony\Component\DependencyInjection\Container + @@ -63,5 +63,21 @@ %kernel.secret% + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml index 4e609a06e5d95..428ba0c8ee48a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml @@ -13,16 +13,16 @@ - + %test.client.parameters% - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 0007a360c6e46..14226ba93083f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -45,6 +45,11 @@ %kernel.debug% + + + + + Symfony\Component\Translation\TranslatorInterface @@ -154,7 +159,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_debug.xml index 37b5cd8de85f5..fc7d2faaa9e8e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation_debug.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index ccfd44e5ca483..a18099f973177 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -37,6 +37,17 @@ %validator.mapping.cache.prefix% + The "%service_id%" service is deprecated since Symfony 2.5 and will be removed in 3.0. + + + + + + + %validator.mapping.cache.prefix% + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index c1f73e561038a..f2123f4a6510f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -36,9 +36,9 @@ + %kernel.default_locale% - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php index 4ffa2e84ff24c..56b3dd92846d3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php @@ -1,7 +1,7 @@ id="escape($id) ?>" name="escape($full_name) ?>" disabled="disabled" $v): ?> -escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> +escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> escape($k), $view->escape($k)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php index 9dac32fc994c0..4b63876984506 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php @@ -1,4 +1,4 @@ $name, '%id%' => $id)) : $view['form']->humanize($name); } ?> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php index 0c04641a835f8..92298cf5fcff5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php @@ -7,7 +7,7 @@ )) ?> multiple="multiple" > - + 0): ?> block($form, 'choice_widget_options', array('choices' => $preferred_choices)) ?> 0 && null !== $separator): ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_start.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_start.html.php index e7b23d394daec..ba2f3a4791987 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_start.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_start.html.php @@ -1,6 +1,6 @@ - $v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?> enctype="multipart/form-data"> + action="" $v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?> enctype="multipart/form-data"> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php new file mode 100644 index 0000000000000..4c628f8e005bc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php @@ -0,0 +1 @@ +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'range')); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php index e90298701c682..6e2f74836e6f0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php @@ -1,13 +1,13 @@ -id="escape($id) ?>" name="escape($full_name) ?>" readonly="readonly" -disabled="disabled" -required="required" +id="escape($id) ?>" name="escape($full_name) ?>" readonly="readonly" + disabled="disabled" + required="required" $v): ?> -escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> +escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> -escape($k), $view->escape($k)) ?> +escape($k), $view->escape($k)) ?> -escape($k), $view->escape($v)) ?> +escape($k), $view->escape($v)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php index 7aa9e9d406d02..c4dff7b61f4e0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php @@ -1,7 +1,7 @@ id="escape($id) ?>" $v): ?> -escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> +escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> escape($k), $view->escape($k)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php index 397528d2b5c71..2f258db49d7e6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php @@ -31,10 +31,22 @@ class DelegatingLoader extends BaseDelegatingLoader protected $logger; private $loading = false; - public function __construct(ControllerNameParser $parser, LoggerInterface $logger = null, LoaderResolverInterface $resolver) + /** + * Ability to pass a LoggerInterface instance as second argument will be removed in 3.0. + * + * @param ControllerNameParser $parser A ControllerNameParser instance + * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance + */ + public function __construct(ControllerNameParser $parser, $resolver, $r = null) { $this->parser = $parser; - $this->logger = $logger; + + if (!$resolver instanceof LoaderResolverInterface) { + $this->logger = $resolver; + $resolver = $r; + + @trigger_error('Passing a LoggerInterface instance as the second argument of the '.__METHOD__.' method is deprecated since Symfony 2.8 and will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } parent::__construct($resolver); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RouterHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RouterHelper.php index 32db60c2758c9..b772916e24ea7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RouterHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RouterHelper.php @@ -31,9 +31,9 @@ public function __construct(UrlGeneratorInterface $router) /** * Generates a URL from the given parameters. * - * @param string $name The name of the route - * @param mixed $parameters An array of parameters - * @param bool|string $referenceType The type of reference (one of the constants in UrlGeneratorInterface) + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface) * * @return string The generated URL * @@ -41,9 +41,43 @@ public function __construct(UrlGeneratorInterface $router) */ public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the "path" or "url" method instead.', E_USER_DEPRECATED); + return $this->generator->generate($name, $parameters, $referenceType); } + /** + * Generates a URL reference (as an absolute or relative path) to the route with the given parameters. + * + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param bool $relative Whether to generate a relative or absolute path + * + * @return string The generated URL reference + * + * @see UrlGeneratorInterface + */ + public function path($name, $parameters = array(), $relative = false) + { + return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * Generates a URL reference (as an absolute URL or network path) to the route with the given parameters. + * + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param bool $schemeRelative Whether to omit the scheme in the generated URL reference + * + * @return string The generated URL reference + * + * @see UrlGeneratorInterface + */ + public function url($name, $parameters = array(), $schemeRelative = false) + { + return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index 045e05f534f04..9720f49afa61a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Test; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ResettableContainerInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\KernelInterface; @@ -172,7 +173,11 @@ protected static function createKernel(array $options = array()) protected static function ensureKernelShutdown() { if (null !== static::$kernel) { + $container = static::$kernel->getContainer(); static::$kernel->shutdown(); + if ($container instanceof ResettableContainerInterface) { + $container->reset(); + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php index a25aa8e637e44..6614f3daa2608 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php @@ -14,7 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Tests\Command\CacheClearCommand\Fixture\TestAppKernel; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; -use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\ConfigCacheFactory; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; @@ -56,15 +56,13 @@ public function testCacheIsFreshAfterCacheClearedWithWarmup() $metaFiles = $finder->files()->in($this->kernel->getCacheDir())->name('*.php.meta'); // simply check that cache is warmed up $this->assertNotEmpty($metaFiles); + $configCacheFactory = new ConfigCacheFactory(true); + $that = $this; + foreach ($metaFiles as $file) { - $configCache = new ConfigCache(substr($file, 0, -5), true); - $this->assertTrue( - $configCache->isFresh(), - sprintf( - 'Meta file "%s" is not fresh', - (string) $file - ) - ); + $configCacheFactory->cache(substr($file, 0, -5), function () use ($that, $file) { + $that->fail(sprintf('Meta file "%s" is not fresh', (string) $file)); + }); } // check that app kernel file present in meta file of container's cache diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php index 1a9e87e1ceeb8..6d32a37fd0ec9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php @@ -26,7 +26,7 @@ public function testDebugAllRoutes() $ret = $tester->execute(array('name' => null), array('decorated' => false)); $this->assertEquals(0, $ret, 'Returns 0 in case of success'); - $this->assertContains('[router] Current routes', $tester->getDisplay()); + $this->assertContains('Name Method Scheme Host Path', $tester->getDisplay()); } public function testDebugSingleRoute() @@ -35,7 +35,7 @@ public function testDebugSingleRoute() $ret = $tester->execute(array('name' => 'foo'), array('decorated' => false)); $this->assertEquals(0, $ret, 'Returns 0 in case of success'); - $this->assertContains('[router] Route "foo"', $tester->getDisplay()); + $this->assertContains('Route Name | foo', $tester->getDisplay()); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php index 82a33b46d56da..cd15b8c5e82fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php @@ -28,7 +28,7 @@ public function testWithMatchPath() $ret = $tester->execute(array('path_info' => '/foo', 'foo'), array('decorated' => false)); $this->assertEquals(0, $ret, 'Returns 0 in case of success'); - $this->assertContains('[router] Route "foo"', $tester->getDisplay()); + $this->assertContains('Route Name | foo', $tester->getDisplay()); } public function testWithNotMatchPath() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php index bf48bde142353..f09623a308190 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php @@ -29,6 +29,23 @@ public function testDumpMessagesAndClean() $tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo'))); $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true)); $this->assertRegExp('/foo/', $tester->getDisplay()); + $this->assertRegExp('/1 message was successfully extracted/', $tester->getDisplay()); + } + + public function testDumpTwoMessagesAndClean() + { + $tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo', 'bar' => 'bar'))); + $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true)); + $this->assertRegExp('/foo/', $tester->getDisplay()); + $this->assertRegExp('/bar/', $tester->getDisplay()); + $this->assertRegExp('/2 messages were successfully extracted/', $tester->getDisplay()); + } + + public function testWriteMessages() + { + $tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo'))); + $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--force' => true)); + $this->assertRegExp('/Translation files were successfully updated./', $tester->getDisplay()); } protected function setUp() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php index 7f2723a0525ae..0adc3e80e312b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -12,7 +12,9 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -76,6 +78,9 @@ public function testLegacyDescribeSynchronizedServiceDefinition(Definition $defi $this->assertDescription($expectedDescription, $definition); } + /** + * @group legacy + */ public function provideLegacySynchronizedServiceDefinitionTestData() { return $this->getDescriptionTestData(ObjectsProvider::getLegacyContainerDefinitions()); @@ -149,6 +154,11 @@ private function assertDescription($expectedDescription, $describedObject, array { $options['raw_output'] = true; $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + + if ('txt' === $this->getFormat()) { + $options['output'] = new SymfonyStyle(new ArrayInput(array()), $output); + } + $this->getDescriptor()->describe($output, $describedObject, $options); if ('json' === $this->getFormat()) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index 108e53a7dff28..0bf193d5c5575 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -166,8 +166,8 @@ public static function getEventDispatchers() { $eventDispatcher = new EventDispatcher(); - $eventDispatcher->addListener('event1', 'global_function'); - $eventDispatcher->addListener('event1', function () { return 'Closure'; }); + $eventDispatcher->addListener('event1', 'global_function', 255); + $eventDispatcher->addListener('event1', function () { return 'Closure'; }, -1); $eventDispatcher->addListener('event2', new CallableClass()); return array('event_dispatcher_1' => $eventDispatcher); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php index cb813c61d62ac..ccf904d0b5e8d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php @@ -126,6 +126,86 @@ private function getContainerWithTokenStorage($token = null) return $container; } + public function testIsGranted() + { + $authorizationChecker = $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface')->getMock(); + $authorizationChecker->expects($this->once())->method('isGranted')->willReturn(true); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->will($this->returnValue(true)); + $container->expects($this->at(1))->method('get')->will($this->returnValue($authorizationChecker)); + + $controller = new TestController(); + $controller->setContainer($container); + + $this->assertTrue($controller->isGranted('foo')); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AccessDeniedException + */ + public function testdenyAccessUnlessGranted() + { + $authorizationChecker = $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface')->getMock(); + $authorizationChecker->expects($this->once())->method('isGranted')->willReturn(false); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->will($this->returnValue(true)); + $container->expects($this->at(1))->method('get')->will($this->returnValue($authorizationChecker)); + + $controller = new TestController(); + $controller->setContainer($container); + + $controller->denyAccessUnlessGranted('foo'); + } + + public function testRenderViewTwig() + { + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + $twig->expects($this->once())->method('render')->willReturn('bar'); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->will($this->returnValue(false)); + $container->expects($this->at(1))->method('has')->will($this->returnValue(true)); + $container->expects($this->at(2))->method('get')->will($this->returnValue($twig)); + + $controller = new TestController(); + $controller->setContainer($container); + + $this->assertEquals('bar', $controller->renderView('foo')); + } + + public function testRenderTwig() + { + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + $twig->expects($this->once())->method('render')->willReturn('bar'); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->will($this->returnValue(false)); + $container->expects($this->at(1))->method('has')->will($this->returnValue(true)); + $container->expects($this->at(2))->method('get')->will($this->returnValue($twig)); + + $controller = new TestController(); + $controller->setContainer($container); + + $this->assertEquals('bar', $controller->render('foo')->getContent()); + } + + public function testStreamTwig() + { + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->will($this->returnValue(false)); + $container->expects($this->at(1))->method('has')->will($this->returnValue(true)); + $container->expects($this->at(2))->method('get')->will($this->returnValue($twig)); + + $controller = new TestController(); + $controller->setContainer($container); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $controller->stream('foo')); + } + public function testRedirectToRoute() { $router = $this->getMockBuilder('Symfony\Component\Routing\RouterInterface')->getMock(); @@ -215,7 +295,8 @@ public function testRenderViewTemplating() $templating->expects($this->once())->method('render')->willReturn('bar'); $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); - $container->expects($this->at(0))->method('get')->will($this->returnValue($templating)); + $container->expects($this->at(0))->method('has')->willReturn(true); + $container->expects($this->at(1))->method('get')->will($this->returnValue($templating)); $controller = new Controller(); $controller->setContainer($container); @@ -229,7 +310,8 @@ public function testRenderTemplating() $templating->expects($this->once())->method('renderResponse')->willReturn(new Response('bar')); $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); - $container->expects($this->at(0))->method('get')->will($this->returnValue($templating)); + $container->expects($this->at(0))->method('has')->willReturn(true); + $container->expects($this->at(1))->method('get')->will($this->returnValue($templating)); $controller = new Controller(); $controller->setContainer($container); @@ -242,7 +324,8 @@ public function testStreamTemplating() $templating = $this->getMockBuilder('Symfony\Component\Routing\RouterInterface')->getMock(); $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); - $container->expects($this->at(0))->method('get')->will($this->returnValue($templating)); + $container->expects($this->at(0))->method('has')->willReturn(true); + $container->expects($this->at(1))->method('get')->will($this->returnValue($templating)); $controller = new Controller(); $controller->setContainer($container); @@ -316,6 +399,16 @@ public function getUser() return parent::getUser(); } + public function isGranted($attributes, $object = null) + { + return parent::isGranted($attributes, $object); + } + + public function denyAccessUnlessGranted($attributes, $object = null, $message = 'Access Denied.') + { + parent::denyAccessUnlessGranted($attributes, $object, $message); + } + public function redirectToRoute($route, array $parameters = array(), $status = 302) { return parent::redirectToRoute($route, $parameters, $status); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/TemplateControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/TemplateControllerTest.php new file mode 100644 index 0000000000000..6afb5e3203f28 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/TemplateControllerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\HttpFoundation\Response; + +/** + * @author Kévin Dunglas + */ +class TemplateControllerTest extends TestCase +{ + public function testTwig() + { + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + $twig->expects($this->once())->method('render')->willReturn('bar'); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->will($this->returnValue(false)); + $container->expects($this->at(1))->method('has')->will($this->returnValue(true)); + $container->expects($this->at(2))->method('get')->will($this->returnValue($twig)); + + $controller = new TemplateController(); + $controller->setContainer($container); + + $this->assertEquals('bar', $controller->templateAction('mytemplate')->getContent()); + } + + public function testTemplating() + { + $templating = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Templating\EngineInterface')->getMock(); + $templating->expects($this->once())->method('renderResponse')->willReturn(new Response('bar')); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->willReturn(true); + $container->expects($this->at(1))->method('get')->will($this->returnValue($templating)); + + $controller = new TemplateController(); + $controller->setContainer($container); + + $this->assertEquals('bar', $controller->templateAction('mytemplate')->getContent()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You can not use the TemplateController if the Templating Component or the Twig Bundle are not available. + */ + public function testNoTwigNorTemplating() + { + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->at(0))->method('has')->willReturn(false); + $container->expects($this->at(1))->method('has')->willReturn(false); + + $controller = new TemplateController(); + $controller->setContainer($container); + + $controller->templateAction('mytemplate')->getContent(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php new file mode 100644 index 0000000000000..a3b602eeaa932 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; + +class ConfigCachePassTest extends TestCase +{ + public function testThatCheckersAreProcessedInPriorityOrder() + { + $services = array( + 'checker_2' => array(0 => array('priority' => 100)), + 'checker_1' => array(0 => array('priority' => 200)), + 'checker_3' => array(), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds', 'getDefinition', 'hasDefinition'))->getMock(); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + $container->expects($this->atLeastOnce()) + ->method('getDefinition') + ->with('config_cache_factory') + ->will($this->returnValue($definition)); + + $definition->expects($this->once()) + ->method('replaceArgument') + ->with(0, array( + new Reference('checker_1'), + new Reference('checker_2'), + new Reference('checker_3'), + )); + + $pass = new ConfigCachePass(); + $pass->process($container); + } + + public function testThatCheckersCanBeMissing() + { + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds'))->getMock(); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue(array())); + + $pass = new ConfigCachePass(); + $pass->process($container); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php new file mode 100644 index 0000000000000..216df2b579390 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\AbstractType; + +/** + * @author Bernhard Schussek + */ +class FormPassTest extends TestCase +{ + public function testDoNothingIfFormExtensionNotLoaded() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $container->compile(); + + $this->assertFalse($container->hasDefinition('form.extension')); + } + + public function testAddTaggedTypes() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition(__CLASS__.'_Type1'); + $definition1->addTag('form.type'); + $definition2 = new Definition(__CLASS__.'_Type2'); + $definition2->addTag('form.type'); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.type1', $definition1); + $container->setDefinition('my.type2', $definition2); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertEquals(array( + // As of Symfony 2.8, the class is used to look up types + __CLASS__.'_Type1' => 'my.type1', + __CLASS__.'_Type2' => 'my.type2', + // Before Symfony 2.8, the service ID was used as default alias + 'my.type1' => 'my.type1', + 'my.type2' => 'my.type2', + ), $extDefinition->getArgument(1)); + } + + /** + * @group legacy + */ + public function testUseCustomAliasIfSet() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition(__CLASS__.'_Type1'); + $definition1->addTag('form.type', array('alias' => 'mytype1')); + $definition2 = new Definition(__CLASS__.'_Type2'); + $definition2->addTag('form.type', array('alias' => 'mytype2')); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.type1', $definition1); + $container->setDefinition('my.type2', $definition2); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertEquals(array( + __CLASS__.'_Type1' => 'my.type1', + __CLASS__.'_Type2' => 'my.type2', + 'mytype1' => 'my.type1', + 'mytype2' => 'my.type2', + ), $extDefinition->getArgument(1)); + } + + public function testAddTaggedTypeExtensions() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension', array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $container->setDefinition('form.extension', $extDefinition); + $container->register('my.type_extension1', 'stdClass') + ->addTag('form.type_extension', array('extended_type' => 'type1')); + $container->register('my.type_extension2', 'stdClass') + ->addTag('form.type_extension', array('extended_type' => 'type1')); + $container->register('my.type_extension3', 'stdClass') + ->addTag('form.type_extension', array('extended_type' => 'type2')); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertSame(array( + 'type1' => array( + 'my.type_extension1', + 'my.type_extension2', + ), + 'type2' => array( + 'my.type_extension3', + ), + ), $extDefinition->getArgument(2)); + } + + /** + * @group legacy + */ + public function testAliasOptionForTaggedTypeExtensions() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension', array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $container->setDefinition('form.extension', $extDefinition); + $container->register('my.type_extension1', 'stdClass') + ->addTag('form.type_extension', array('alias' => 'type1')); + $container->register('my.type_extension2', 'stdClass') + ->addTag('form.type_extension', array('alias' => 'type2')); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertSame(array( + 'type1' => array( + 'my.type_extension1', + ), + 'type2' => array( + 'my.type_extension2', + ), + ), $extDefinition->getArgument(2)); + } + + public function testAddTaggedGuessers() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition('stdClass'); + $definition1->addTag('form.type_guesser'); + $definition2 = new Definition('stdClass'); + $definition2->addTag('form.type_guesser'); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.guesser1', $definition1); + $container->setDefinition('my.guesser2', $definition2); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertSame(array( + 'my.guesser1', + 'my.guesser2', + ), $extDefinition->getArgument(3)); + } + + /** + * @dataProvider privateTaggedServicesProvider + */ + public function testPrivateTaggedServices($id, $tagName, $expectedExceptionMessage) + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $container->setDefinition('form.extension', $extDefinition); + $container->register($id, 'stdClass')->setPublic(false)->addTag($tagName); + + if (method_exists($this, 'expectException')) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage($expectedExceptionMessage); + } else { + $this->setExpectedException('InvalidArgumentException', $expectedExceptionMessage); + } + + $container->compile(); + } + + public function privateTaggedServicesProvider() + { + return array( + array('my.type', 'form.type', 'The service "my.type" must be public as form types are lazy-loaded'), + array('my.type_extension', 'form.type_extension', 'The service "my.type_extension" must be public as form type extensions are lazy-loaded'), + array('my.guesser', 'form.type_guesser', 'The service "my.guesser" must be public as form type guessers are lazy-loaded'), + ); + } +} + +class FormPassTest_Type1 extends AbstractType +{ +} + +class FormPassTest_Type2 extends AbstractType +{ +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/PropertyInfoPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/PropertyInfoPassTest.php new file mode 100644 index 0000000000000..a7801e0f6938f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/PropertyInfoPassTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass; +use Symfony\Component\DependencyInjection\Reference; + +class PropertyInfoPassTest extends TestCase +{ + public function testServicesAreOrderedAccordingToPriority() + { + $services = array( + 'n3' => array(array()), + 'n1' => array(array('priority' => 200)), + 'n2' => array(array('priority' => 100)), + ); + + $expected = array( + new Reference('n1'), + new Reference('n2'), + new Reference('n3'), + ); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds'))->getMock(); + + $container->expects($this->any()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $propertyInfoPass = new PropertyInfoPass(); + + $method = new \ReflectionMethod( + 'Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass', + 'findAndSortTaggedServices' + ); + $method->setAccessible(true); + + $actual = $method->invoke($propertyInfoPass, 'tag', $container); + + $this->assertEquals($expected, $actual); + } + + public function testReturningEmptyArrayWhenNoService() + { + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds'))->getMock(); + + $container->expects($this->any()) + ->method('findTaggedServiceIds') + ->will($this->returnValue(array())); + + $propertyInfoPass = new PropertyInfoPass(); + + $method = new \ReflectionMethod( + 'Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass', + 'findAndSortTaggedServices' + ); + $method->setAccessible(true); + + $actual = $method->invoke($propertyInfoPass, 'tag', $container); + + $this->assertEquals(array(), $actual); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php new file mode 100644 index 0000000000000..b9e8f18845c0d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; + +class UnusedTagsPassTest extends TestCase +{ + public function testProcess() + { + $pass = new UnusedTagsPass(); + + $formatter = $this->getMockBuilder('Symfony\Component\DependencyInjection\Compiler\LoggingFormatter')->getMock(); + $formatter + ->expects($this->at(0)) + ->method('format') + ->with($pass, 'Tag "kenrel.event_subscriber" was defined on service(s) "foo", "bar", but was never used. Did you mean "kernel.event_subscriber"?') + ; + + $compiler = $this->getMockBuilder('Symfony\Component\DependencyInjection\Compiler\Compiler')->getMock(); + $compiler->expects($this->once())->method('getLoggingFormatter')->will($this->returnValue($formatter)); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds', 'getCompiler', 'findUnusedTags', 'findTags'))->getMock(); + $container->expects($this->once())->method('getCompiler')->will($this->returnValue($compiler)); + $container->expects($this->once()) + ->method('findTags') + ->will($this->returnValue(array('kenrel.event_subscriber'))); + $container->expects($this->once()) + ->method('findUnusedTags') + ->will($this->returnValue(array('kenrel.event_subscriber', 'form.type'))); + $container->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('kenrel.event_subscriber') + ->will($this->returnValue(array( + 'foo' => array(), + 'bar' => array(), + ))); + + $pass->process($container); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 6505d5a034932..0d8d4992d5504 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -208,6 +208,7 @@ protected static function getBundleDefaultConfig() 'enabled' => false, 'fallbacks' => array('en'), 'logging' => true, + 'paths' => array(), ), 'validation' => array( 'enabled' => false, @@ -229,6 +230,9 @@ protected static function getBundleDefaultConfig() 'magic_call' => false, 'throw_exception_on_invalid_index' => false, ), + 'property_info' => array( + 'enabled' => false, + ), 'assets' => array( 'version' => null, 'version_format' => '%%s?%%s', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 6861844b49233..13decebe16236 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -35,7 +35,8 @@ 'cookie_path' => '/', 'cookie_domain' => 'example.com', 'cookie_secure' => true, - 'cookie_httponly' => true, + 'cookie_httponly' => false, + 'use_cookies' => true, 'gc_maxlifetime' => 90000, 'gc_divisor' => 108, 'gc_probability' => 1, @@ -53,17 +54,23 @@ 'translator' => array( 'enabled' => true, 'fallback' => 'fr', + 'paths' => array('%kernel.root_dir%/Fixtures/translations'), ), 'validation' => array( 'enabled' => true, - 'cache' => 'apc', + 'cache' => 'validator.mapping.cache.doctrine.apc', ), 'annotations' => array( 'cache' => 'file', 'debug' => true, 'file_cache_dir' => '%kernel.cache_dir%/annotations', ), - 'serializer' => array('enabled' => true), + 'serializer' => array( + 'enabled' => true, + 'enable_annotations' => true, + 'cache' => 'serializer.mapping.cache.apc', + 'name_converter' => 'serializer.name_converter.camel_case_to_snake_case', + ), 'ide' => 'file%%link%%format', 'request' => array( 'formats' => array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/property_info.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/property_info.php new file mode 100644 index 0000000000000..847e15fa194ff --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/property_info.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'property_info' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_disabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_disabled.php new file mode 100644 index 0000000000000..dedd090beb774 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_disabled.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'serializer' => array( + 'enabled' => false, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_enabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_enabled.php new file mode 100644 index 0000000000000..eadad57ec7180 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_enabled.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'serializer' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/translations/test_paths.en.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/translations/test_paths.en.yml new file mode 100644 index 0000000000000..d4e682c47c9ca --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/translations/test_paths.en.yml @@ -0,0 +1,2 @@ +custom: + paths: test diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index c0d8a85a1c5d7..4147ec917f37e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -15,7 +15,7 @@ - + text/csv @@ -35,9 +35,11 @@ theme2 - - + + %kernel.root_dir%/Fixtures/translations + + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/property_info.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/property_info.xml new file mode 100644 index 0000000000000..825bc46e74477 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/property_info.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_disabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_disabled.xml new file mode 100644 index 0000000000000..73f1dccb1a6ed --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_disabled.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_enabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_enabled.xml new file mode 100644 index 0000000000000..e202fc1b5ee20 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_enabled.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 3e000ca9cc59c..46860412582b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -26,7 +26,8 @@ framework: cookie_path: / cookie_domain: example.com cookie_secure: true - cookie_httponly: true + cookie_httponly: false + use_cookies: true gc_probability: 1 gc_divisor: 108 gc_maxlifetime: 90000 @@ -41,14 +42,19 @@ framework: translator: enabled: true fallback: fr + paths: ['%kernel.root_dir%/Fixtures/translations'] validation: enabled: true - cache: apc + cache: validator.mapping.cache.doctrine.apc annotations: cache: file debug: true file_cache_dir: '%kernel.cache_dir%/annotations' - serializer: { enabled: true } + serializer: + enabled: true + enable_annotations: true + cache: serializer.mapping.cache.apc + name_converter: serializer.name_converter.camel_case_to_snake_case ide: file%%link%%format request: formats: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/property_info.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/property_info.yml new file mode 100644 index 0000000000000..fbdf7a7b0d715 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/property_info.yml @@ -0,0 +1,3 @@ +framework: + property_info: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_disabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_disabled.yml new file mode 100644 index 0000000000000..330e19a6976e7 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_disabled.yml @@ -0,0 +1,3 @@ +framework: + serializer: + enabled: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_enabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_enabled.yml new file mode 100644 index 0000000000000..40a1ff7d65b3b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_enabled.yml @@ -0,0 +1,3 @@ +framework: + serializer: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 95be6c91d8587..7f350aabee0fd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -70,6 +70,7 @@ public function testCsrfProtectionForFormsEnablesCsrfProtectionAutomatically() $this->assertTrue($container->hasDefinition('security.csrf.token_manager')); } + /** @group legacy */ public function testSecureRandomIsAvailableIfCsrfIsDisabled() { $container = $this->createContainerFromFile('csrf_disabled'); @@ -175,7 +176,8 @@ public function testSession() $this->assertEquals('/', $options['cookie_path']); $this->assertEquals('example.com', $options['cookie_domain']); $this->assertTrue($options['cookie_secure']); - $this->assertTrue($options['cookie_httponly']); + $this->assertFalse($options['cookie_httponly']); + $this->assertTrue($options['use_cookies']); $this->assertEquals(108, $options['gc_divisor']); $this->assertEquals(1, $options['gc_probability']); $this->assertEquals(90000, $options['gc_maxlifetime']); @@ -268,9 +270,14 @@ public function testTranslator() $files, '->registerTranslatorConfiguration() finds Security translation resources' ); + $this->assertContains( + strtr(__DIR__.'/Fixtures/translations/test_paths.en.yml', '/', DIRECTORY_SEPARATOR), + $files, + '->registerTranslatorConfiguration() finds translation resources in custom paths' + ); $calls = $container->getDefinition('translator.default')->getMethodCalls(); - $this->assertEquals(array('fr'), $calls[0][1][0]); + $this->assertEquals(array('fr'), $calls[1][1][0]); } public function testTranslatorMultipleFallbacks() @@ -278,7 +285,7 @@ public function testTranslatorMultipleFallbacks() $container = $this->createContainerFromFile('translator_fallbacks'); $calls = $container->getDefinition('translator.default')->getMethodCalls(); - $this->assertEquals(array('en', 'fr'), $calls[0][1][0]); + $this->assertEquals(array('en', 'fr'), $calls[1][1][0]); } /** @@ -312,7 +319,7 @@ public function testValidation() $this->assertSame('addMethodMapping', $calls[4][0]); $this->assertSame(array('loadValidatorMetadata'), $calls[4][1]); $this->assertSame('setMetadataCache', $calls[5][0]); - $this->assertEquals(array(new Reference('validator.mapping.cache.apc')), $calls[5][1]); + $this->assertEquals(array(new Reference('validator.mapping.cache.doctrine.apc')), $calls[5][1]); } /** @@ -337,8 +344,9 @@ public function testAnnotations() { $container = $this->createContainerFromFile('full'); - $this->assertEquals($container->getParameter('kernel.cache_dir').'/annotations', $container->getDefinition('annotations.file_cache_reader')->getArgument(1)); - $this->assertInstanceOf('Doctrine\Common\Annotations\FileCacheReader', $container->get('annotation_reader')); + $this->assertEquals($container->getParameter('kernel.cache_dir').'/annotations', $container->getDefinition('annotations.filesystem_cache')->getArgument(0)); + $this->assertSame('annotations.cached_reader', (string) $container->getAlias('annotation_reader')); + $this->assertSame('annotations.filesystem_cache', (string) $container->getDefinition('annotations.cached_reader')->getArgument(1)); } public function testFileLinkFormat() @@ -510,6 +518,13 @@ public function testSerializerEnabled() { $container = $this->createContainerFromFile('full'); $this->assertTrue($container->has('serializer')); + + $argument = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0); + + $this->assertCount(1, $argument); + $this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass()); + $this->assertEquals(new Reference('serializer.mapping.cache.apc'), $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1)); + $this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1)); } public function testObjectNormalizerRegistered() @@ -539,6 +554,32 @@ public function testAssetHelperWhenTemplatesAreEnabledAndAssetsAreDisabled() $this->assertSame('assets.packages', (string) $packages); } + public function testSerializerServiceIsRegisteredWhenEnabled() + { + $container = $this->createContainerFromFile('serializer_enabled'); + + $this->assertTrue($container->hasDefinition('serializer')); + } + + public function testSerializerServiceIsNotRegisteredWhenDisabled() + { + $container = $this->createContainerFromFile('serializer_disabled'); + + $this->assertFalse($container->hasDefinition('serializer')); + } + + public function testPropertyInfoDisabled() + { + $container = $this->createContainerFromFile('default_config'); + $this->assertFalse($container->has('property_info')); + } + + public function testPropertyInfoEnabled() + { + $container = $this->createContainerFromFile('property_info'); + $this->assertTrue($container->has('property_info')); + } + protected function createContainer(array $data = array()) { return new ContainerBuilder(new ParameterBag(array_merge(array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt index 3d40ba0084340..4be149bffbe07 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt @@ -1 +1,3 @@ -This service is an alias for the service service_1 + + // This service is an alias for the service service_1 + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt index df99e817c96b5..0dbee6ac67c85 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt @@ -1 +1,3 @@ -This service is an alias for the service service_2 + + // This service is an alias for the service service_2 + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/array_parameter.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/array_parameter.txt index 182037f2469dd..903717b620cff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/array_parameter.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/array_parameter.txt @@ -1 +1,5 @@ -["bootstrap_3_horizontal_layout.html.twig","bootstrap_3_layo... + --------------------- ----------------------------------------------------------------- +  Parameter   Value  + --------------------- ----------------------------------------------------------------- + twig.form.resources ["bootstrap_3_horizontal_layout.html.twig","bootstrap_3_layo... + --------------------- ----------------------------------------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json index 5c2038df42c91..4ce716e640d2e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json @@ -6,8 +6,11 @@ "public": true, "synthetic": false, "lazy": true, + "shared": true, "synchronized": false, "abstract": true, + "autowire": false, + "autowiring_types": [], "file": null, "factory_class": "Full\\Qualified\\FactoryClass", "factory_method": "get", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md index 96fe55184014a..b2879c150f941 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md @@ -11,8 +11,10 @@ Definitions - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: no - Abstract: yes +- Autowired: no - Factory Class: `Full\Qualified\FactoryClass` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.txt index 5fdbdfd439f2f..f03cb62bede8c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.txt @@ -1,6 +1,11 @@ -[container] Public services - Service ID  Class name  - alias_1 alias for "service_1" - alias_2 alias for "service_2" - definition_1 Full\Qualified\Class1 - service_container Symfony\Component\DependencyInjection\ContainerBuilder +Symfony Container Public Services +================================= + + ------------------- -------------------------------------------------------- +  Service ID   Class name  + ------------------- -------------------------------------------------------- + alias_1 alias for "service_1" + alias_2 alias for "service_2" + definition_1 Full\Qualified\Class1 + service_container Symfony\Component\DependencyInjection\ContainerBuilder + ------------------- -------------------------------------------------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml index b21190dc7983e..d29265535f4af 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml @@ -2,7 +2,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json index 6dc56b3e2335b..e979c42f041cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json @@ -6,8 +6,11 @@ "public": true, "synthetic": false, "lazy": true, + "shared": true, "synchronized": false, "abstract": true, + "autowire": false, + "autowiring_types": [], "file": null, "factory_class": "Full\\Qualified\\FactoryClass", "factory_method": "get", @@ -19,8 +22,11 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, + "autowire": false, + "autowiring_types": [], "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md index 12938a6e0e123..ba24c9a01d3ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md @@ -11,8 +11,10 @@ Definitions - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: no - Abstract: yes +- Autowired: no - Factory Class: `Full\Qualified\FactoryClass` - Factory Method: `get` @@ -23,8 +25,10 @@ Definitions - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no +- Autowired: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt index 662664fc97f37..4434b248fdeb3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt @@ -1,7 +1,12 @@ -[container] Public and private services - Service ID  Class name  - alias_1 alias for "service_1" - alias_2 alias for "service_2" - definition_1 Full\Qualified\Class1 - definition_2 Full\Qualified\Class2 - service_container Symfony\Component\DependencyInjection\ContainerBuilder +Symfony Container Public and Private Services +============================================= + + ------------------- -------------------------------------------------------- +  Service ID   Class name  + ------------------- -------------------------------------------------------- + alias_1 alias for "service_1" + alias_2 alias for "service_2" + definition_1 Full\Qualified\Class1 + definition_2 Full\Qualified\Class2 + service_container Symfony\Component\DependencyInjection\ContainerBuilder + ------------------- -------------------------------------------------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml index 7aecc4f629e7f..ae4ad244e4ad2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml @@ -2,10 +2,10 @@ - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json index af2c1044d0ed0..e0aa26b6708c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json @@ -6,8 +6,11 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, + "autowire": false, + "autowiring_types": [], "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md index 0b90618a476a2..9cda082852ac9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md @@ -11,8 +11,10 @@ Definitions - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no +- Autowired: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.txt index b506c5c922845..e3f6ccbb02564 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.txt @@ -1,4 +1,9 @@ -[container] Public and private services with tag tag1 - Service ID  attr1 attr2 attr3 Class name  - definition_2 val1 val2 Full\Qualified\Class2 - " val3 +Symfony Container Public and Private Services Tagged with "tag1" Tag +==================================================================== + + -------------- ------- ------- ------- ----------------------- +  Service ID   attr1   attr2   attr3   Class name  + -------------- ------- ------- ------- ----------------------- + definition_2 val1 val2 Full\Qualified\Class2 + " val3 + -------------- ------- ------- ------- ----------------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml index d6ac0b750b169..4ca9bff33a37b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml @@ -1,6 +1,6 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json index 3837b95cb89e9..736b099f7d430 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json @@ -6,8 +6,11 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, + "autowire": false, + "autowiring_types": [], "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get" @@ -20,8 +23,11 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, + "autowire": false, + "autowiring_types": [], "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get" diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md index 1a3ed9ca85302..7372687a41963 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md @@ -11,8 +11,10 @@ tag1 - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no +- Autowired: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` @@ -28,8 +30,10 @@ tag2 - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no +- Autowired: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.txt index 791c1d95c3c9e..f187bbdd709e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.txt @@ -1,6 +1,12 @@ -[container] Tagged services -[tag] tag1 -definition_2 +Symfony Container Public and Private Tags +========================================= -[tag] tag2 -definition_2 +"tag1" tag +---------- + + * definition_2 + +"tag2" tag +---------- + + * definition_2 \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml index be9d2f015bd2c..2bb8803066798 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml @@ -1,12 +1,12 @@ - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json index c15b4a6d29060..0056b4353abf3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json @@ -4,8 +4,11 @@ "public": true, "synthetic": false, "lazy": true, + "shared": true, "synchronized": false, "abstract": true, + "autowire": false, + "autowiring_types": [], "file": null, "factory_class": "Full\\Qualified\\FactoryClass", "factory_method": "get", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md index 68d3569732c61..caee1dc28c7d2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md @@ -3,7 +3,9 @@ - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: no - Abstract: yes +- Autowired: no - Factory Class: `Full\Qualified\FactoryClass` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt index af495497dd35d..cf3f4a21ab078 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt @@ -1,11 +1,17 @@ -Service Id - -Class Full\Qualified\Class1 -Tags - -Scope container -Public yes -Synthetic no -Lazy yes -Synchronized no -Abstract yes -Factory Class Full\Qualified\FactoryClass -Factory Method get +------------------ ----------------------------- +  Option   Value  + ------------------ ----------------------------- + Service ID - + Class Full\Qualified\Class1 + Tags - + Scope container + Public yes + Synthetic no + Lazy yes + Synchronized no + Abstract yes + Autowired no + Autowiring Types - + Factory Class Full\Qualified\FactoryClass + Factory Method get + ------------------ ----------------------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml index 92a9bbd70bd30..1e6a6cadfcd8e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json index 62bcac9031f60..1b465f270e2e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json @@ -4,8 +4,11 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, + "autowire": false, + "autowiring_types": [], "file": "\/path\/to\/file", "factory_service": "factory.service", "factory_method": "get", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md index 6b2f14651d300..3b426441fd151 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md @@ -3,8 +3,10 @@ - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no +- Autowired: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt index 28a00d583b090..4605072470b87 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt @@ -1,15 +1,18 @@ -Service Id - -Class Full\Qualified\Class2 -Tags - - tag1 (attr1: val1, attr2: val2) - - tag1 (attr3: val3) - - tag2 () -Scope container -Public no -Synthetic yes -Lazy no -Synchronized no -Abstract no -Required File /path/to/file -Factory Service factory.service -Factory Method get +------------------ ------------------------------------------------------- +  Option   Value  + ------------------ ------------------------------------------------------- + Service ID - + Class Full\Qualified\Class2 + Tags tag1 (attr1: val1, attr2: val2)tag1 (attr3: val3)tag2 + Scope container + Public no + Synthetic yes + Lazy no + Synchronized no + Abstract no + Autowired no + Autowiring Types - + Required File /path/to/file + Factory Service factory.service + Factory Method get + ------------------ ------------------------------------------------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml index f128e522e5990..0689cb6cba3ca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml @@ -1,5 +1,5 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json index e40e130d453cb..4b68f0cefc0e4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json @@ -1,9 +1,11 @@ [ { "type": "function", - "name": "global_function" + "name": "global_function", + "priority": 255 }, { - "type": "closure" + "type": "closure", + "priority": -1 } ] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md index 206c44f717526..98b81ecdce422 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md @@ -4,7 +4,9 @@ - Type: `function` - Name: `global_function` +- Priority: `255` ## Listener 2 - Type: `closure` +- Priority: `-1` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt index 22b17a19cfb91..d411b672271cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt @@ -1,8 +1,9 @@ -[event_dispatcher] Registered listeners for event event1 +Registered Listeners for "event1" Event +======================================= -+-------+-------------------+ -| Order | Callable | -+-------+-------------------+ -| #1 | global_function() | -| #2 | \Closure() | -+-------+-------------------+ + ------- ------------------- ---------- +  Order   Callable   Priority  + ------- ------------------- ---------- + #1 global_function() 255 + #2 \Closure() -1 + ------- ------------------- ---------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml index 4806f1f1280c7..bc03189af7b80 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml @@ -1,5 +1,5 @@ - - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json index 56fc7a4f1e546..30772d9a4a212 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json @@ -2,16 +2,19 @@ "event1": [ { "type": "function", - "name": "global_function" + "name": "global_function", + "priority": 255 }, { - "type": "closure" + "type": "closure", + "priority": -1 } ], "event2": [ { "type": "object", - "name": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass" + "name": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass", + "priority": 0 } ] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md index ad4b79e3117fa..eb809789d5f17 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md @@ -6,10 +6,12 @@ - Type: `function` - Name: `global_function` +- Priority: `255` ### Listener 2 - Type: `closure` +- Priority: `-1` ## event2 @@ -17,3 +19,4 @@ - Type: `object` - Name: `Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\CallableClass` +- Priority: `0` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt index 95a5b4648e939..52155be1cea36 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt @@ -1,16 +1,21 @@ -[event_dispatcher] Registered listeners by event +Registered Listeners Grouped by Event +===================================== -[Event] event1 -+-------+-------------------+ -| Order | Callable | -+-------+-------------------+ -| #1 | global_function() | -| #2 | \Closure() | -+-------+-------------------+ +"event1" event +-------------- -[Event] event2 -+-------+-----------------------------------------------------------------------------------+ -| Order | Callable | -+-------+-----------------------------------------------------------------------------------+ -| #1 | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\CallableClass::__invoke() | -+-------+-----------------------------------------------------------------------------------+ + ------- ------------------- ---------- +  Order   Callable   Priority  + ------- ------------------- ---------- + #1 global_function() 255 + #2 \Closure() -1 + ------- ------------------- ---------- + +"event2" event +-------------- + + ------- ----------------------------------------------------------------------------------- ---------- +  Order   Callable   Priority  + ------- ----------------------------------------------------------------------------------- ---------- + #1 Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\CallableClass::__invoke() 0 + ------- ----------------------------------------------------------------------------------- ---------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml index 3e4b20d823798..d7443f9743666 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml @@ -1,10 +1,10 @@ - - + + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json index 6372d9e5b56df..730b81f3a87d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json @@ -1,15 +1,16 @@ { - "class": "Full\\Qualified\\Class1", - "scope": "container", - "public": true, - "synthetic": false, - "lazy": true, - "synchronized": true, - "abstract": true, - "file": null, - "factory_class": "Full\\Qualified\\FactoryClass", - "factory_method": "get", - "tags": [ - - ] + "class": "Full\\Qualified\\Class1", + "scope": "container", + "public": true, + "synthetic": false, + "lazy": true, + "shared": true, + "synchronized": true, + "abstract": true, + "autowire": false, + "autowiring_types": [], + "file": null, + "factory_class": "Full\\Qualified\\FactoryClass", + "factory_method": "get", + "tags": [] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md index d9832a1511ab2..76671ff4fed8f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md @@ -3,7 +3,9 @@ - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: yes - Abstract: yes +- Autowired: no - Factory Class: `Full\Qualified\FactoryClass` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt index 3d9cbb2077c3b..fd56bcbea7586 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt @@ -1,11 +1,17 @@ -Service Id - -Class Full\Qualified\Class1 -Tags - -Scope container -Public yes -Synthetic no -Lazy yes -Synchronized yes -Abstract yes -Factory Class Full\Qualified\FactoryClass -Factory Method get +------------------ ----------------------------- +  Option   Value  + ------------------ ----------------------------- + Service ID - + Class Full\Qualified\Class1 + Tags - + Scope container + Public yes + Synthetic no + Lazy yes + Synchronized yes + Abstract yes + Autowired no + Autowiring Types - + Factory Class Full\Qualified\FactoryClass + Factory Method get + ------------------ ----------------------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml index 75d0820244579..cfedcaade90ea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml @@ -1,2 +1,2 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json index 278a5bfed413b..1b465f270e2e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json @@ -1,33 +1,34 @@ { - "class": "Full\\Qualified\\Class2", - "scope": "container", - "public": false, - "synthetic": true, - "lazy": false, - "synchronized": false, - "abstract": false, - "file": "\/path\/to\/file", - "factory_service": "factory.service", - "factory_method": "get", - "tags": [ - { - "name": "tag1", - "parameters": { - "attr1": "val1", - "attr2": "val2" - } - }, - { - "name": "tag1", - "parameters": { - "attr3": "val3" - } - }, - { - "name": "tag2", - "parameters": [ - - ] - } - ] + "class": "Full\\Qualified\\Class2", + "scope": "container", + "public": false, + "synthetic": true, + "lazy": false, + "shared": true, + "synchronized": false, + "abstract": false, + "autowire": false, + "autowiring_types": [], + "file": "\/path\/to\/file", + "factory_service": "factory.service", + "factory_method": "get", + "tags": [ + { + "name": "tag1", + "parameters": { + "attr1": "val1", + "attr2": "val2" + } + }, + { + "name": "tag1", + "parameters": { + "attr3": "val3" + } + }, + { + "name": "tag2", + "parameters": [] + } + ] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md index f552debbf18bc..7730f9e7daa84 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md @@ -3,8 +3,10 @@ - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no +- Autowired: no - File: `/path/to/file` - Factory Service: `factory.service` - Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt index 28a00d583b090..4605072470b87 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt @@ -1,15 +1,18 @@ -Service Id - -Class Full\Qualified\Class2 -Tags - - tag1 (attr1: val1, attr2: val2) - - tag1 (attr3: val3) - - tag2 () -Scope container -Public no -Synthetic yes -Lazy no -Synchronized no -Abstract no -Required File /path/to/file -Factory Service factory.service -Factory Method get +------------------ ------------------------------------------------------- +  Option   Value  + ------------------ ------------------------------------------------------- + Service ID - + Class Full\Qualified\Class2 + Tags tag1 (attr1: val1, attr2: val2)tag1 (attr3: val3)tag2 + Scope container + Public no + Synthetic yes + Lazy no + Synchronized no + Abstract no + Autowired no + Autowiring Types - + Required File /path/to/file + Factory Service factory.service + Factory Method get + ------------------ ------------------------------------------------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml index dd3e2e06d7174..1f6b7a23767a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml @@ -1,5 +1,5 @@ - + val1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.txt index 6bc5995f62e34..53e34624d0628 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.txt @@ -1 +1,5 @@ -symfony \ No newline at end of file +--------------- --------- +  Parameter   Value  + --------------- --------- + database_name symfony + --------------- --------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.txt index 8a1b02c17876a..d363cd2e99be4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.txt @@ -1,6 +1,11 @@ -[container] List of parameters - Parameter Value  - array [12,"Hello world!",true] - boolean true - integer 12 - string Hello world! +Symfony Container Parameters +============================ + + ----------- -------------------------- +  Parameter   Value  + ----------- -------------------------- + array [12,"Hello world!",true] + boolean true + integer 12 + string Hello world! + ----------- -------------------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt index ba7458d143c84..25074dfd18b2c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt @@ -1,12 +1,17 @@ -Path /hello/{name} -Path Regex #PATH_REGEX# -Host localhost -Host Regex #HOST_REGEX# -Scheme http|https -Method GET|HEAD -Class Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub -Defaults name: Joseph -Requirements name: [a-z]+ -Options compiler_class: Symfony\Component\Routing\RouteCompiler - opt1: val1 - opt2: val2 ++--------------+-------------------------------------------------------------------+ +| Property | Value | ++--------------+-------------------------------------------------------------------+ +| Route Name | | +| Path | /hello/{name} | +| Path Regex | #PATH_REGEX# | +| Host | localhost | +| Host Regex | #HOST_REGEX# | +| Scheme | http|https | +| Method | GET|HEAD | +| Requirements | name: [a-z]+ | +| Class | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub | +| Defaults | name: Joseph | +| Options | compiler_class: Symfony\Component\Routing\RouteCompiler | +| | opt1: val1 | +| | opt2: val2 | ++--------------+-------------------------------------------------------------------+ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt index 8e4e0cb009c3a..5593cc0d81ab0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt @@ -1,12 +1,17 @@ -Path /name/add -Path Regex #PATH_REGEX# -Host localhost -Host Regex #HOST_REGEX# -Scheme http|https -Method PUT|POST -Class Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub -Defaults NONE -Requirements NO CUSTOM -Options compiler_class: Symfony\Component\Routing\RouteCompiler - opt1: val1 - opt2: val2 ++--------------+-------------------------------------------------------------------+ +| Property | Value | ++--------------+-------------------------------------------------------------------+ +| Route Name | | +| Path | /name/add | +| Path Regex | #PATH_REGEX# | +| Host | localhost | +| Host Regex | #HOST_REGEX# | +| Scheme | http|https | +| Method | PUT|POST | +| Requirements | NO CUSTOM | +| Class | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub | +| Defaults | NONE | +| Options | compiler_class: Symfony\Component\Routing\RouteCompiler | +| | opt1: val1 | +| | opt2: val2 | ++--------------+-------------------------------------------------------------------+ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.txt index 31c796685bbff..95e83240ec05a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.txt @@ -1,4 +1,6 @@ -[router] Current routes - Name  Method  Scheme  Host  Path  - route_1 GET|HEAD http|https localhost /hello/{name} - route_2 PUT|POST http|https localhost /name/add +--------- ---------- ------------ ----------- --------------- +  Name   Method   Scheme   Host   Path  + --------- ---------- ------------ ----------- --------------- + route_1 GET|HEAD http|https localhost /hello/{name} + route_2 PUT|POST http|https localhost /name/add + --------- ---------- ------------ ----------- --------------- \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php index 5a7cd354763d0..1dcc5536c19b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php @@ -32,18 +32,18 @@ array('%count%' => 10) ) ?> -trans('other-domain-test-no-params-short-array', [], 'not_messages'); ?> +trans('other-domain-test-no-params-short-array', array(), 'not_messages'); ?> trans('other-domain-test-no-params-long-array', array(), 'not_messages'); ?> -trans('other-domain-test-params-short-array', ['foo' => 'bar'], 'not_messages'); ?> +trans('other-domain-test-params-short-array', array('foo' => 'bar'), 'not_messages'); ?> trans('other-domain-test-params-long-array', array('foo' => 'bar'), 'not_messages'); ?> -transChoice('other-domain-test-trans-choice-short-array-%count%', 10, ['%count%' => 10], 'not_messages'); ?> +transChoice('other-domain-test-trans-choice-short-array-%count%', 10, array('%count%' => 10), 'not_messages'); ?> transChoice('other-domain-test-trans-choice-long-array-%count%', 10, array('%count%' => 10), 'not_messages'); ?> -trans('typecast', ['a' => (int) '123'], 'not_messages'); ?> -transChoice('msg1', 10 + 1, [], 'not_messages'); ?> -transChoice('msg2', ceil(4.5), [], 'not_messages'); ?> +trans('typecast', array('a' => (int) '123'), 'not_messages'); ?> +transChoice('msg1', 10 + 1, array(), 'not_messages'); ?> +transChoice('msg2', ceil(4.5), array(), 'not_messages'); ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php new file mode 100644 index 0000000000000..c02a6b84e519c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Component\PropertyInfo\Type; + +class PropertyInfoTest extends WebTestCase +{ + public function testPhpDocPriority() + { + static::bootKernel(array('test_case' => 'Serializer')); + $container = static::$kernel->getContainer(); + + $this->assertEquals(array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT))), $container->get('property_info')->getTypes('Symfony\Bundle\FrameworkBundle\Tests\Functional\Dummy', 'codes')); + } +} + +class Dummy +{ + /** + * @param int[] $codes + */ + public function setCodes(array $codes) + { + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/bundles.php new file mode 100644 index 0000000000000..144db90236034 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return array( + new FrameworkBundle(), +); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml new file mode 100644 index 0000000000000..cac135c315d00 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml @@ -0,0 +1,6 @@ +imports: + - { resource: ../config/default.yml } + +framework: + serializer: { enabled: true } + property_info: { enabled: true } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/ConcreteMicroKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/ConcreteMicroKernel.php new file mode 100644 index 0000000000000..02bed823823e4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/ConcreteMicroKernel.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Kernel; + +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Routing\RouteCollectionBuilder; + +class ConcreteMicroKernel extends Kernel +{ + use MicroKernelTrait; + + private $cacheDir; + + public function halloweenAction() + { + return new Response('halloween'); + } + + public function registerBundles() + { + return array( + new FrameworkBundle(), + ); + } + + public function getCacheDir() + { + return $this->cacheDir = sys_get_temp_dir().'/sf_micro_kernel'; + } + + public function getLogDir() + { + return $this->cacheDir; + } + + public function __destruct() + { + $fs = new Filesystem(); + $fs->remove($this->cacheDir); + } + + protected function configureRoutes(RouteCollectionBuilder $routes) + { + $routes->add('/', 'kernel:halloweenAction'); + } + + protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) + { + $c->loadFromExtension('framework', array( + 'secret' => '$ecret', + )); + + $c->setParameter('halloween', 'Have a great day!'); + $c->register('halloween', 'stdClass'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php new file mode 100644 index 0000000000000..2b716150b801c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Kernel; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; + +class MicroKernelTraitTest extends TestCase +{ + /** + * @requires PHP 5.4 + */ + public function test() + { + $kernel = new ConcreteMicroKernel('test', true); + $kernel->boot(); + + $request = Request::create('/'); + $response = $kernel->handle($request); + + $this->assertEquals('halloween', $response->getContent()); + $this->assertEquals('Have a great day!', $kernel->getContainer()->getParameter('halloween')); + $this->assertInstanceOf('stdClass', $kernel->getContainer()->get('halloween')); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php new file mode 100644 index 0000000000000..bd85b9ed94b69 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php @@ -0,0 +1,46 @@ +controllerNameParser = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @group legacy + */ + public function testLegacyConstructorApi() + { + new DelegatingLoader($this->controllerNameParser, new NullLogger(), new LoaderResolver()); + $this->assertTrue(true, '__construct() accepts a LoggerInterface instance as its second argument'); + } + + /** + * @group legacy + */ + public function testLegacyConstructorApiAcceptsNullAsSecondArgument() + { + new DelegatingLoader($this->controllerNameParser, null, new LoaderResolver()); + $this->assertTrue(true, '__construct() accepts null as its second argument'); + } + + public function testConstructorApi() + { + new DelegatingLoader($this->controllerNameParser, new LoaderResolver()); + $this->assertTrue(true, '__construct() takes a ControllerNameParser and LoaderResolverInterface respectively as its first and second argument.'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index b1e8efaa6d90d..80d1e696d88de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @@ -61,12 +61,36 @@ protected function tearDown() parent::tearDown(); } + public function testStartTagHasNoActionAttributeWhenActionIsEmpty() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagHasActionAttributeWhenActionIsZero() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '0', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + public function testMoneyWidgetInIso() { $this->engine->setCharset('ISO-8859-1'); $view = $this->factory - ->createNamed('name', 'money') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\MoneyType') ->createView() ; @@ -140,39 +164,4 @@ public static function themeInheritanceProvider() array(array('TestBundle:Parent'), array('TestBundle:Child')), ); } - - public function testRange() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testRangeWithMinMaxValues() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testLabelWithoutTranslationOnButton() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testButtonlabelWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php index 3448a56fa4d2d..19f4912e0b5aa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @@ -31,6 +31,30 @@ class FormHelperTableLayoutTest extends AbstractTableLayoutTest 'choice_attr', ); + public function testStartTagHasNoActionAttributeWhenActionIsEmpty() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagHasActionAttributeWhenActionIsZero() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => '0', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + protected function getExtensions() { // should be moved to the Form component once absolute file paths are supported @@ -115,39 +139,4 @@ protected function setTheme(FormView $view, array $themes) { $this->engine->get('form')->setTheme($view, $themes); } - - public function testRange() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testRangeWithMinMaxValues() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testLabelWithoutTranslationOnButton() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testButtonlabelWithoutTranslation() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } - - public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() - { - $this->markTestIncomplete('No-op for forward compatibility with AbstractLayoutTest 2.8'); - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index b910cd9ed33e9..06bd65eae4746 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -18,35 +18,39 @@ "require": { "php": ">=5.3.9", "ext-xml": "*", - "symfony/asset": "~2.7", - "symfony/dependency-injection": "^2.6.2", - "symfony/config": "~2.4", - "symfony/event-dispatcher": "~2.5", - "symfony/finder": "^2.0.5", + "symfony/asset": "~2.7|~3.0.0", + "symfony/class-loader": "~2.1|~3.0.0", + "symfony/dependency-injection": "~2.8.41", + "symfony/config": "~2.8", + "symfony/event-dispatcher": "~2.8|~3.0.0", + "symfony/finder": "^2.0.5|~3.0.0", "symfony/http-foundation": "~2.7.36|^2.8.29", - "symfony/http-kernel": "~2.7.29|^2.8.22", - "symfony/filesystem": "~2.3", - "symfony/routing": "~2.7.24|^2.8.17", - "symfony/security-core": "~2.6.13|~2.7.9|~2.8", - "symfony/security-csrf": "~2.6", - "symfony/stopwatch": "~2.3", - "symfony/templating": "~2.7", - "symfony/translation": "~2.7", + "symfony/http-kernel": "^2.8.22", + "symfony/polyfill-mbstring": "~1.0", + "symfony/filesystem": "~2.3|~3.0.0", + "symfony/routing": "^2.8.17", + "symfony/security-core": "~2.6.13|~2.7.9|~2.8|~3.0.0", + "symfony/security-csrf": "^2.8.31|^3.3.13", + "symfony/stopwatch": "~2.3|~3.0.0", + "symfony/templating": "~2.7|~3.0.0", + "symfony/translation": "~2.8", + "doctrine/cache": "~1.0", "doctrine/annotations": "~1.0" }, "require-dev": { - "symfony/browser-kit": "~2.4", - "symfony/console": "~2.7", - "symfony/css-selector": "^2.0.5", - "symfony/dom-crawler": "^2.0.5", - "symfony/intl": "~2.3", - "symfony/security": "~2.6", - "symfony/form": "~2.7.26|^2.8.19", - "symfony/class-loader": "~2.1", - "symfony/expression-language": "~2.6", - "symfony/process": "^2.0.5", - "symfony/validator": "~2.5", - "symfony/yaml": "^2.0.5", + "symfony/browser-kit": "~2.4|~3.0.0", + "symfony/console": "~2.8.19|~3.2.7", + "symfony/css-selector": "^2.0.5|~3.0.0", + "symfony/dom-crawler": "^2.0.5|~3.0.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/form": "^2.8.19", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/process": "^2.0.5|~3.0.0", + "symfony/validator": "~2.5|~3.0.0", + "symfony/yaml": "^2.0.5|~3.0.0", + "symfony/property-info": "~2.8|~3.0.0", + "phpdocumentor/reflection": "^1.0.7", + "twig/twig": "~1.34|~2.4", "sensio/framework-extra-bundle": "^3.0.2" }, "conflict": { @@ -58,8 +62,8 @@ "symfony/serializer": "For using the serializer service", "symfony/validator": "For using validation", "symfony/yaml": "For using the debug:config and lint:yaml commands", - "symfony/process": "For using the server:run, server:start, server:stop, and server:status commands", - "doctrine/cache": "For using alternative cache drivers" + "symfony/property-info": "For using the property_info service", + "symfony/process": "For using the server:run, server:start, server:stop, and server:status commands" }, "autoload": { "psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" }, @@ -70,7 +74,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 82879d4cf29b9..11873ee9fae7d 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,13 @@ CHANGELOG ========= +2.8.0 +----- + + * deprecated the `key` setting of `anonymous`, `remember_me` and `http_digest` + in favor of the `secret` setting. + * deprecated the `intention` firewall listener setting in favor of the `csrf_token_id`. + 2.6.0 ----- diff --git a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php index 342ae791475e2..7151175d938e7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php @@ -84,9 +84,9 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); - $input->isInteractive() ? $output->title('Symfony Password Encoder Utility') : $output->newLine(); + $input->isInteractive() ? $io->title('Symfony Password Encoder Utility') : $io->newLine(); $password = $input->getArgument('password'); $userClass = $input->getArgument('user-class'); @@ -101,12 +101,12 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!$password) { if (!$input->isInteractive()) { - $output->error('The password must not be empty.'); + $io->error('The password must not be empty.'); return 1; } $passwordQuestion = $this->createPasswordQuestion(); - $password = $output->askQuestion($passwordQuestion); + $password = $io->askQuestion($passwordQuestion); } $salt = null; @@ -114,9 +114,9 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($input->isInteractive() && !$emptySalt) { $emptySalt = true; - $output->note('The command will take care of generating a salt for you. Be aware that some encoders advise to let them generate their own salt. If you\'re using one of those encoders, please answer \'no\' to the question below. '.PHP_EOL.'Provide the \'empty-salt\' option in order to let the encoder handle the generation itself.'); + $io->note('The command will take care of generating a salt for you. Be aware that some encoders advise to let them generate their own salt. If you\'re using one of those encoders, please answer \'no\' to the question below. '.PHP_EOL.'Provide the \'empty-salt\' option in order to let the encoder handle the generation itself.'); - if ($output->confirm('Confirm salt generation ?')) { + if ($io->confirm('Confirm salt generation ?')) { $salt = $this->generateSalt(); $emptySalt = false; } @@ -133,15 +133,15 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!$emptySalt) { $rows[] = array('Generated salt', $salt); } - $output->table(array('Key', 'Value'), $rows); + $io->table(array('Key', 'Value'), $rows); if (!$emptySalt) { - $output->note(sprintf('Make sure that your salt storage field fits the salt length: %s chars', strlen($salt))); + $io->note(sprintf('Make sure that your salt storage field fits the salt length: %s chars', strlen($salt))); } elseif ($bcryptWithoutEmptySalt) { - $output->note('Bcrypt encoder used: the encoder generated its own built-in salt.'); + $io->note('Bcrypt encoder used: the encoder generated its own built-in salt.'); } - $output->success('Password encoding succeeded'); + $io->success('Password encoding succeeded'); } /** @@ -164,6 +164,6 @@ private function createPasswordQuestion() private function generateSalt() { - return base64_encode($this->getContainer()->get('security.secure_random')->nextBytes(30)); + return base64_encode(random_bytes(30)); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index ca2c8d6db3c75..6ac33dbb925ff 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\Security\Core\Role\RoleInterface; +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; /** * @author Fabien Potencier @@ -25,11 +26,13 @@ class SecurityDataCollector extends DataCollector { private $tokenStorage; private $roleHierarchy; + private $logoutUrlGenerator; - public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null) + public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null) { $this->tokenStorage = $tokenStorage; $this->roleHierarchy = $roleHierarchy; + $this->logoutUrlGenerator = $logoutUrlGenerator; } /** @@ -42,6 +45,7 @@ public function collect(Request $request, Response $response, \Exception $except 'enabled' => false, 'authenticated' => false, 'token_class' => null, + 'logout_url' => null, 'user' => '', 'roles' => array(), 'inherited_roles' => array(), @@ -52,6 +56,7 @@ public function collect(Request $request, Response $response, \Exception $except 'enabled' => true, 'authenticated' => false, 'token_class' => null, + 'logout_url' => null, 'user' => '', 'roles' => array(), 'inherited_roles' => array(), @@ -60,6 +65,7 @@ public function collect(Request $request, Response $response, \Exception $except } else { $inheritedRoles = array(); $assignedRoles = $token->getRoles(); + if (null !== $this->roleHierarchy) { $allRoles = $this->roleHierarchy->getReachableRoles($assignedRoles); foreach ($allRoles as $role) { @@ -68,10 +74,21 @@ public function collect(Request $request, Response $response, \Exception $except } } } + + $logoutUrl = null; + try { + if (null !== $this->logoutUrlGenerator) { + $logoutUrl = $this->logoutUrlGenerator->getLogoutPath(); + } + } catch (\Exception $e) { + // fail silently when the logout URL cannot be generated + } + $this->data = array( 'enabled' => true, 'authenticated' => $token->isAuthenticated(), 'token_class' => get_class($token), + 'logout_url' => $logoutUrl, 'user' => $token->getUsername(), 'roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $assignedRoles), 'inherited_roles' => array_unique(array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles)), @@ -151,6 +168,16 @@ public function getTokenClass() return $this->data['token_class']; } + /** + * Get the logout URL. + * + * @return string The logout URL + */ + public function getLogoutUrl() + { + return $this->data['logout_url']; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php index f8cc92c6c7c1d..f4c494c1255a5 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\LogicException; /** * Adds all configured security voters to the access decision manager. @@ -40,6 +41,10 @@ public function process(ContainerBuilder $container) krsort($voters); $voters = call_user_func_array('array_merge', $voters); - $container->getDefinition('security.access.decision_manager')->replaceArgument(0, $voters); + if (!$voters) { + throw new LogicException('No security voters found. You need to tag at least one with "security.voter"'); + } + + $container->getDefinition('security.access.decision_manager')->addMethodCall('setVoters', array($voters)); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 43bd560588fa6..db43883aaf72e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -210,6 +210,11 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->prototype('scalar')->end() ->end() ->booleanNode('security')->defaultTrue()->end() + ->scalarNode('user_checker') + ->defaultValue('security.user_checker') + ->treatNullLike('security.user_checker') + ->info('The UserChecker to use when authenticating users in this firewall.') + ->end() ->scalarNode('request_matcher')->end() ->scalarNode('access_denied_url')->end() ->scalarNode('access_denied_handler')->end() @@ -231,6 +236,8 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->beforeNormalization() ->ifTrue(function ($v) { return isset($v['csrf_provider']); }) ->then(function ($v) { + @trigger_error("Setting the 'csrf_provider' configuration key on a security firewall is deprecated since Symfony 2.8 and will be removed in 3.0. Use the 'csrf_token_generator' configuration key instead.", E_USER_DEPRECATED); + $v['csrf_token_generator'] = $v['csrf_provider']; unset($v['csrf_provider']); @@ -240,6 +247,8 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->beforeNormalization() ->ifTrue(function ($v) { return isset($v['intention']); }) ->then(function ($v) { + @trigger_error("Setting the 'intention' configuration key on a security firewall is deprecated since Symfony 2.8 and will be removed in 3.0. Use the 'csrf_token_id' key instead.", E_USER_DEPRECATED); + $v['csrf_token_id'] = $v['intention']; unset($v['intention']); @@ -280,8 +289,22 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->end() ->arrayNode('anonymous') ->canBeUnset() + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['key']); }) + ->then(function ($v) { + if (isset($v['secret'])) { + throw new \LogicException('Cannot set both key and secret options for security.firewall.anonymous, use only secret instead.'); + } + + @trigger_error('security.firewall.anonymous.key is deprecated since Symfony 2.8 and will be removed in 3.0. Use security.firewall.anonymous.secret instead.', E_USER_DEPRECATED); + + $v['secret'] = $v['key']; + + unset($v['key']); + }) + ->end() ->children() - ->scalarNode('key')->defaultValue(uniqid('', true))->end() + ->scalarNode('secret')->defaultValue(uniqid('', true))->end() ->end() ->end() ->arrayNode('switch_user') diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index b674c47e15bf0..2736fabfcfde4 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -29,7 +29,7 @@ public function __construct() $this->addOption('username_parameter', '_username'); $this->addOption('password_parameter', '_password'); $this->addOption('csrf_parameter', '_csrf_token'); - $this->addOption('intention', 'authenticate'); + $this->addOption('csrf_token_id', 'authenticate'); $this->addOption('post_only', true); } @@ -48,8 +48,38 @@ public function addConfiguration(NodeDefinition $node) parent::addConfiguration($node); $node + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['csrf_provider']) && isset($v['csrf_token_generator']); }) + ->thenInvalid("You should define a value for only one of 'csrf_provider' and 'csrf_token_generator' on a security firewall. Use 'csrf_token_generator' as this replaces 'csrf_provider'.") + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['intention']) && isset($v['csrf_token_id']); }) + ->thenInvalid("You should define a value for only one of 'intention' and 'csrf_token_id' on a security firewall. Use 'csrf_token_id' as this replaces 'intention'.") + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['csrf_provider']); }) + ->then(function ($v) { + @trigger_error("Setting the 'csrf_provider' configuration key on a security firewall is deprecated since Symfony 2.8 and will be removed in 3.0. Use the 'csrf_token_generator' configuration key instead.", E_USER_DEPRECATED); + + $v['csrf_token_generator'] = $v['csrf_provider']; + unset($v['csrf_provider']); + + return $v; + }) + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['intention']); }) + ->then(function ($v) { + @trigger_error("Setting the 'intention' configuration key on a security firewall is deprecated since Symfony 2.8 and will be removed in 3.0. Use the 'csrf_token_id' key instead.", E_USER_DEPRECATED); + + $v['csrf_token_id'] = $v['intention']; + unset($v['intention']); + + return $v; + }) + ->end() ->children() - ->scalarNode('csrf_provider')->cannotBeEmpty()->end() + ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end() ->end() ; } @@ -65,6 +95,7 @@ protected function createAuthProvider(ContainerBuilder $container, $id, $config, $container ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.dao')) ->replaceArgument(0, new Reference($userProviderId)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) ->replaceArgument(2, $id) ; @@ -77,7 +108,7 @@ protected function createListener($container, $id, $config, $userProvider) $container ->getDefinition($listenerId) - ->addArgument(isset($config['csrf_provider']) ? new Reference($config['csrf_provider']) : null) + ->addArgument(isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null) ; return $listenerId; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php new file mode 100644 index 0000000000000..026a3d65ac6ba --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * FormLoginLdapFactory creates services for form login ldap authentication. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + */ +class FormLoginLdapFactory extends FormLoginFactory +{ + protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) + { + $provider = 'security.authentication.provider.ldap_bind.'.$id; + $container + ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind')) + ->replaceArgument(0, new Reference($userProviderId)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) + ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference($config['service'])) + ->replaceArgument(4, $config['dn_string']) + ; + + return $provider; + } + + public function addConfiguration(NodeDefinition $node) + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->end() + ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->end() + ; + } + + public function getKey() + { + return 'form-login-ldap'; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php new file mode 100644 index 0000000000000..533560d6d986d --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.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\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Configures the "guard" authentication provider key under a firewall. + * + * @author Ryan Weaver + */ +class GuardAuthenticationFactory implements SecurityFactoryInterface +{ + public function getPosition() + { + return 'pre_auth'; + } + + public function getKey() + { + return 'guard'; + } + + public function addConfiguration(NodeDefinition $node) + { + $node + ->fixXmlConfig('authenticator') + ->children() + ->scalarNode('provider') + ->info('A key from the "providers" section of your security config, in case your user provider is different than the firewall') + ->end() + ->scalarNode('entry_point') + ->info('A service id (of one of your authenticators) whose start() method should be called when an anonymous user hits a page that requires authentication') + ->defaultValue(null) + ->end() + ->arrayNode('authenticators') + ->info('An array of service ids for all of your "authenticators"') + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->end() + ; + } + + public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) + { + $authenticatorIds = $config['authenticators']; + $authenticatorReferences = array(); + foreach ($authenticatorIds as $authenticatorId) { + $authenticatorReferences[] = new Reference($authenticatorId); + } + + // configure the GuardAuthenticationFactory to have the dynamic constructor arguments + $providerId = 'security.authentication.provider.guard.'.$id; + $container + ->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.guard')) + ->replaceArgument(0, $authenticatorReferences) + ->replaceArgument(1, new Reference($userProvider)) + ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference('security.user_checker.'.$id)) + ; + + // listener + $listenerId = 'security.authentication.listener.guard.'.$id; + $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.guard')); + $listener->replaceArgument(2, $id); + $listener->replaceArgument(3, $authenticatorReferences); + + // determine the entryPointId to use + $entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config); + + // this is always injected - then the listener decides if it should be used + $container + ->getDefinition($listenerId) + ->addTag('security.remember_me_aware', array('id' => $id, 'provider' => $userProvider)); + + return array($providerId, $listenerId, $entryPointId); + } + + private function determineEntryPoint($defaultEntryPointId, array $config) + { + if ($defaultEntryPointId) { + // explode if they've configured the entry_point, but there is already one + if ($config['entry_point']) { + throw new \LogicException(sprintf( + 'The guard authentication provider cannot use the "%s" entry_point because another entry point is already configured by another provider! Either remove the other provider or move the entry_point configuration as a root key under your firewall (i.e. at the same level as "guard").', + $config['entry_point'] + )); + } + + return $defaultEntryPointId; + } + + if ($config['entry_point']) { + // if it's configured explicitly, use it! + return $config['entry_point']; + } + + $authenticatorIds = $config['authenticators']; + if (1 == count($authenticatorIds)) { + // if there is only one authenticator, use that as the entry point + return array_shift($authenticatorIds); + } + + // we have multiple entry points - we must ask them to configure one + throw new \LogicException(sprintf( + 'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of your configurators (%s)', + implode(', ', $authenticatorIds) + )); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php index 0b81f8001b426..162ea05157984 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php @@ -29,6 +29,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $container ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.dao')) ->replaceArgument(0, new Reference($userProvider)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) ->replaceArgument(2, $id) ; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php new file mode 100644 index 0000000000000..f2b1695c83772 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * HttpBasicFactory creates services for HTTP basic authentication. + * + * @author Fabien Potencier + * @author Grégoire Pineau + * @author Charles Sarrazin + */ +class HttpBasicLdapFactory extends HttpBasicFactory +{ + public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) + { + $provider = 'security.authentication.provider.ldap_bind.'.$id; + $container + ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind')) + ->replaceArgument(0, new Reference($userProvider)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) + ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference($config['service'])) + ->replaceArgument(4, $config['dn_string']) + ; + + // entry point + $entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPoint); + + // listener + $listenerId = 'security.authentication.listener.basic.'.$id; + $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.basic')); + $listener->replaceArgument(2, $id); + $listener->replaceArgument(3, new Reference($entryPointId)); + + return array($provider, $listenerId, $entryPointId); + } + + public function addConfiguration(NodeDefinition $node) + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->end() + ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->end() + ; + } + + public function getKey() + { + return 'http-basic-ldap'; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php index 691714fa317e8..4cfb79653c054 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php @@ -29,6 +29,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $container ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.dao')) ->replaceArgument(0, new Reference($userProvider)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) ->replaceArgument(2, $id) ; @@ -58,10 +59,26 @@ public function getKey() public function addConfiguration(NodeDefinition $node) { $node + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['key']); }) + ->then(function ($v) { + if (isset($v['secret'])) { + throw new \LogicException('Cannot set both key and secret options for http_digest, use only secret instead.'); + } + + @trigger_error('http_digest.key is deprecated since Symfony 2.8 and will be removed in 3.0. Use http_digest.secret instead.', E_USER_DEPRECATED); + + $v['secret'] = $v['key']; + + unset($v['key']); + + return $v; + }) + ->end() ->children() ->scalarNode('provider')->end() ->scalarNode('realm')->defaultValue('Secured Area')->end() - ->scalarNode('key')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('secret')->isRequired()->cannotBeEmpty()->end() ->end() ; } @@ -76,7 +93,7 @@ protected function createEntryPoint($container, $id, $config, $defaultEntryPoint $container ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.digest_entry_point')) ->addArgument($config['realm']) - ->addArgument($config['key']) + ->addArgument($config['secret']) ; return $entryPointId; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index a77d872e26c40..bd36a0fca9896 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -35,7 +35,8 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $authProviderId = 'security.authentication.provider.rememberme.'.$id; $container ->setDefinition($authProviderId, new DefinitionDecorator('security.authentication.provider.rememberme')) - ->addArgument($config['key']) + ->replaceArgument(0, new Reference('security.user_checker.'.$id)) + ->addArgument($config['secret']) ->addArgument($id) ; @@ -56,7 +57,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, } $rememberMeServices = $container->setDefinition($rememberMeServicesId, new DefinitionDecorator($templateId)); - $rememberMeServices->replaceArgument(1, $config['key']); + $rememberMeServices->replaceArgument(1, $config['secret']); $rememberMeServices->replaceArgument(2, $id); if (isset($config['token_provider'])) { @@ -121,10 +122,27 @@ public function getKey() public function addConfiguration(NodeDefinition $node) { $node->fixXmlConfig('user_provider'); - $builder = $node->children(); + $builder = $node + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['key']); }) + ->then(function ($v) { + if (isset($v['secret'])) { + throw new \LogicException('Cannot set both key and secret options for remember_me, use only secret instead.'); + } + + @trigger_error('remember_me.key is deprecated since Symfony 2.8 and will be removed in 3.0. Use remember_me.secret instead.', E_USER_DEPRECATED); + + $v['secret'] = $v['key']; + + unset($v['key']); + + return $v; + }) + ->end() + ->children(); $builder - ->scalarNode('key')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('secret')->isRequired()->cannotBeEmpty()->end() ->scalarNode('token_provider')->end() ->arrayNode('user_providers') ->beforeNormalization() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php index 01ac91ce2ce9d..cf2e2ed71b16c 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RemoteUserFactory.php @@ -30,6 +30,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $container ->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.pre_authenticated')) ->replaceArgument(0, new Reference($userProvider)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) ->addArgument($id) ; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php index 27d8c5f050ec5..c1c6e48083856 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php @@ -49,6 +49,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, ->replaceArgument(0, new Reference($config['authenticator'])) ->replaceArgument(1, new Reference($userProvider)) ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference('security.user_checker.'.$id)) ; // listener diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php index f8ca5509d039d..0467ef2ba2c75 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php @@ -29,6 +29,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $container ->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.pre_authenticated')) ->replaceArgument(0, new Reference($userProvider)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) ->addArgument($id) ; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php new file mode 100644 index 0000000000000..8aea9074bd71a --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * LdapFactory creates services for Ldap user provider. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + */ +class LdapFactory implements UserProviderFactoryInterface +{ + public function create(ContainerBuilder $container, $id, $config) + { + $container + ->setDefinition($id, new DefinitionDecorator('security.user.provider.ldap')) + ->replaceArgument(0, new Reference($config['service'])) + ->replaceArgument(1, $config['base_dn']) + ->replaceArgument(2, $config['search_dn']) + ->replaceArgument(3, $config['search_password']) + ->replaceArgument(4, $config['default_roles']) + ->replaceArgument(5, $config['uid_key']) + ->replaceArgument(6, $config['filter']) + ; + } + + public function getKey() + { + return 'ldap'; + } + + public function addConfiguration(NodeDefinition $node) + { + $node + ->children() + ->scalarNode('service')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('base_dn')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('search_dn')->end() + ->scalarNode('search_password')->end() + ->arrayNode('default_roles') + ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->scalarNode('uid_key')->defaultValue('sAMAccountName')->end() + ->scalarNode('filter')->defaultValue('({uid_key}={username})')->end() + ->end() + ; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 9c526f88daa9d..34276e95e79f2 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -65,6 +65,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('templating_php.xml'); $loader->load('templating_twig.xml'); $loader->load('collectors.xml'); + $loader->load('guard.xml'); if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { $container->removeDefinition('security.expression_language'); @@ -99,16 +100,16 @@ public function load(array $configs, ContainerBuilder $container) // add some required classes for compilation $this->addClassesToCompile(array( - 'Symfony\\Component\\Security\\Http\\Firewall', - 'Symfony\\Component\\Security\\Core\\User\\UserProviderInterface', - 'Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager', - 'Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorage', - 'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager', - 'Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker', - 'Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface', - 'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap', - 'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext', - 'Symfony\\Component\\HttpFoundation\\RequestMatcher', + 'Symfony\Component\Security\Http\Firewall', + 'Symfony\Component\Security\Core\User\UserProviderInterface', + 'Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager', + 'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage', + 'Symfony\Component\Security\Core\Authorization\AccessDecisionManager', + 'Symfony\Component\Security\Core\Authorization\AuthorizationChecker', + 'Symfony\Component\Security\Core\Authorization\Voter\VoterInterface', + 'Symfony\Bundle\SecurityBundle\Security\FirewallMap', + 'Symfony\Bundle\SecurityBundle\Security\FirewallContext', + 'Symfony\Component\HttpFoundation\RequestMatcher', )); } @@ -160,7 +161,7 @@ private function configureDbalAclProvider(array $config, ContainerBuilder $conta private function createRoleHierarchy(array $config, ContainerBuilder $container) { - if (!isset($config['role_hierarchy'])) { + if (!isset($config['role_hierarchy']) || 0 === count($config['role_hierarchy'])) { $container->removeDefinition('security.access.role_hierarchy_voter'); return; @@ -293,7 +294,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $logoutListener = $container->setDefinition($logoutListenerId, new DefinitionDecorator('security.logout_listener')); $logoutListener->replaceArgument(3, array( 'csrf_parameter' => $firewall['logout']['csrf_parameter'], - 'intention' => $firewall['logout']['csrf_token_id'], + 'csrf_token_id' => $firewall['logout']['csrf_token_id'], 'logout_path' => $firewall['logout']['path'], )); @@ -363,6 +364,8 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a // Exception listener $exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless'])); + $container->setAlias(new Alias('security.user_checker.'.$id, false), $firewall['user_checker']); + return array($matcher, $listeners, $exceptionListener, null !== $logoutListenerId ? new Reference($logoutListenerId) : null); } @@ -405,7 +408,7 @@ private function createAuthenticationListeners($container, $id, $firewall, &$aut $listenerId = 'security.authentication.listener.anonymous.'.$id; $container ->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.anonymous')) - ->replaceArgument(1, $firewall['anonymous']['key']) + ->replaceArgument(1, $firewall['anonymous']['secret']) ; $listeners[] = new Reference($listenerId); @@ -413,7 +416,7 @@ private function createAuthenticationListeners($container, $id, $firewall, &$aut $providerId = 'security.authentication.provider.anonymous.'.$id; $container ->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.anonymous')) - ->replaceArgument(0, $firewall['anonymous']['key']) + ->replaceArgument(0, $firewall['anonymous']['secret']) ; $authenticationProviders[] = $providerId; @@ -571,6 +574,7 @@ private function createSwitchUserListener($container, $id, $config, $defaultProv $switchUserListenerId = 'security.authentication.switchuser_listener.'.$id; $listener = $container->setDefinition($switchUserListenerId, new DefinitionDecorator('security.authentication.switchuser_listener')); $listener->replaceArgument(1, new Reference($userProvider)); + $listener->replaceArgument(2, new Reference('security.user_checker.'.$id)); $listener->replaceArgument(3, $id); $listener->replaceArgument(6, $config['parameter']); $listener->replaceArgument(7, $config['role']); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml index 8f6a608c6de8e..b781dbf25179e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml @@ -10,9 +10,10 @@ - + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml new file mode 100644 index 0000000000000..80318c243a970 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index b044ccba98e74..029395de9dea0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -47,12 +47,14 @@ Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator Symfony\Component\Security\Core\Authorization\ExpressionLanguage + + The "%service_id%" service is deprecated since Symfony 2.6 and will be removed in 3.0. @@ -159,10 +161,21 @@ + + + + + + + + + + + @@ -170,6 +183,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index 917e90f79281b..f7462045724b3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -217,21 +217,31 @@ - + %security.authentication.hide_user_not_found% + + + + + + + %security.authentication.hide_user_not_found% + + + null - + @@ -251,7 +261,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml index d9fb0d261e44e..7ed64bbaa7338 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml @@ -29,7 +29,7 @@ - + @@ -46,9 +46,7 @@ - - + abstract="true" /> + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig index 923be83810648..dd724682d749e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig @@ -1,92 +1,122 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block page_title 'Security' %} + {% block toolbar %} {% if collector.tokenClass %} - {% set color_code = (collector.enabled and collector.authenticated) ? 'green' : 'yellow' %} - {% set authentication_color_code = (collector.enabled and collector.authenticated) ? 'green' : 'red' %} - {% set authentication_color_text = (collector.enabled and collector.authenticated) ? 'Yes' : 'No' %} + {% set is_authenticated = collector.enabled and collector.authenticated %} + {% set color_code = is_authenticated ? '' : 'yellow' %} {% else %} - {% set color_code = collector.enabled ? 'red' : 'black' %} + {% set color_code = collector.enabled ? 'red' : '' %} {% endif %} + + {% set icon %} + {{ include('@Security/Collector/icon.svg') }} + {{ collector.user|default('n/a') }} + {% endset %} + {% set text %} {% if collector.tokenClass %}
Logged in as - {{ collector.user }} + {{ collector.user }}
+
Authenticated - {{ authentication_color_text }} + {{ is_authenticated ? 'Yes' : 'No' }}
+ {% if collector.tokenClass != null %}
Token class - {{ collector.tokenClass|abbr_class }} + {{ collector.tokenClass|abbr_class }} +
+ {% endif %} + {% if collector.logoutUrl %} +
+ Actions + Logout
{% endif %} {% elseif collector.enabled %} - You are not authenticated. +
+ You are not authenticated. +
{% else %} - The security is disabled. +
+ The security is disabled. +
{% endif %} {% endset %} - {% set icon %} - - - {% if collector.user %}
{{ collector.user }}
{% endif %} - {% endset %} - {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: color_code }) }} {% endblock %} {% block menu %} - - - Security - + + {{ include('@Security/Collector/icon.svg') }} + Security + {% endblock %} {% block panel %} -

Security

+

Security Token

+ {% if collector.tokenClass %} +
+
+ {{ collector.user == 'anon.' ? 'Anonymous' : collector.user }} + Username +
+ +
+ {{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} + Authenticated +
+
+ - - - - - - - - - - - - - {% if collector.supportsRoleHierarchy %} - - - - - {% endif %} - {% if collector.tokenClass != null %} - - - - - {% endif %} + + + + + + + + + + + + + {% if collector.supportsRoleHierarchy %} + + + + + {% endif %} + + {% if collector.tokenClass %} + + + + + {% endif %} +
Username{{ collector.user }}
Authenticated? - {% if collector.authenticated %} - yes - {% else %} - no {% if not collector.roles|length %}(probably because the user has no roles){% endif %} - {% endif %} -
Roles{{ collector.roles|yaml_encode }}
Inherited Roles{{ collector.inheritedRoles|yaml_encode }}
Token class{{ collector.tokenClass }}
PropertyValue
Roles + {{ collector.roles is empty ? 'none' : collector.roles|yaml_encode }} + + {% if not collector.authenticated and collector.roles is empty %} +

User is not authenticated probably because they have no roles.

+ {% endif %} +
Inherited Roles{{ collector.inheritedRoles is empty ? 'none' : collector.inheritedRoles|yaml_encode }}
Token class{{ collector.tokenClass }}
{% elseif collector.enabled %} -

- No token -

+
+

There is no security token.

+
{% else %} -

- The security component is disabled -

+
+

The security component is disabled.

+
{% endif %} {% endblock %} diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 137558569bc2d..18f5db433e750 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -18,7 +18,9 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory; @@ -26,6 +28,8 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimplePreAuthenticationFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory; /** * Bundle. @@ -40,15 +44,19 @@ public function build(ContainerBuilder $container) $extension = $container->getExtension('security'); $extension->addSecurityListenerFactory(new FormLoginFactory()); + $extension->addSecurityListenerFactory(new FormLoginLdapFactory()); $extension->addSecurityListenerFactory(new HttpBasicFactory()); + $extension->addSecurityListenerFactory(new HttpBasicLdapFactory()); $extension->addSecurityListenerFactory(new HttpDigestFactory()); $extension->addSecurityListenerFactory(new RememberMeFactory()); $extension->addSecurityListenerFactory(new X509Factory()); $extension->addSecurityListenerFactory(new RemoteUserFactory()); $extension->addSecurityListenerFactory(new SimplePreAuthenticationFactory()); $extension->addSecurityListenerFactory(new SimpleFormFactory()); + $extension->addSecurityListenerFactory(new GuardAuthenticationFactory()); $extension->addUserProviderFactory(new InMemoryFactory()); + $extension->addUserProviderFactory(new LdapFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php index c57207b13f95e..6e58a8ce6e579 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php @@ -44,6 +44,8 @@ public function testThatSecurityVotersAreProcessedInPriorityOrder() $compilerPass = new AddSecurityVotersPass(); $compilerPass->process($container); + $calls = $container->getDefinition('security.access.decision_manager')->getMethodCalls(); + $this->assertEquals( array( new Reference('highest_prio_service'), @@ -51,7 +53,7 @@ public function testThatSecurityVotersAreProcessedInPriorityOrder() new Reference('no_prio_service'), new Reference('zero_prio_service'), ), - $container->getDefinition('security.access.decision_manager')->getArgument(0) + $calls[0][1][0] ); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 371baa704a835..77f3ab2606943 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -93,6 +93,20 @@ public function testFirewalls() 'security.authentication.listener.anonymous.host', 'security.access_listener', ), + array( + 'security.channel_listener', + 'security.context_listener.1', + 'security.authentication.listener.basic.with_user_checker', + 'security.authentication.listener.anonymous.with_user_checker', + 'security.access_listener', + ), + array( + 'security.channel_listener', + 'security.context_listener.2', + 'security.authentication.listener.simple_form.simple_auth', + 'security.authentication.listener.anonymous.simple_auth', + 'security.access_listener', + ), ), $listeners); } @@ -233,6 +247,21 @@ public function testRememberMeThrowExceptions() $this->assertFalse($service->getArgument(5)); } + public function testUserCheckerConfig() + { + $this->assertEquals('app.user_checker', $this->getContainer('container1')->getAlias('security.user_checker.with_user_checker')); + } + + public function testUserCheckerConfigWithDefaultChecker() + { + $this->assertEquals('security.user_checker', $this->getContainer('container1')->getAlias('security.user_checker.host')); + } + + public function testUserCheckerConfigWithNoCheckers() + { + $this->assertEquals('security.user_checker', $this->getContainer('container1')->getAlias('security.user_checker.secure')); + } + protected function getContainer($file) { $file = $file.'.'.$this->getFileExtension(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php index b16a46ff03457..54ac7b20c47f5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -64,14 +64,15 @@ 'simple' => array('pattern' => '/login', 'security' => false), 'secure' => array('stateless' => true, 'http_basic' => true, - 'http_digest' => array('key' => 'TheKey'), + 'http_digest' => array('secret' => 'TheSecret'), 'form_login' => true, 'anonymous' => true, 'switch_user' => true, 'x509' => true, 'remote_user' => true, 'logout' => true, - 'remember_me' => array('key' => 'TheKey'), + 'remember_me' => array('secret' => 'TheSecret'), + 'user_checker' => null, ), 'host' => array( 'pattern' => '/test', @@ -80,6 +81,16 @@ 'anonymous' => true, 'http_basic' => true, ), + 'with_user_checker' => array( + 'user_checker' => 'app.user_checker', + 'anonymous' => true, + 'http_basic' => true, + ), + 'simple_auth' => array( + 'provider' => 'default', + 'anonymous' => true, + 'simple_form' => array('authenticator' => 'simple_authenticator'), + ), ), 'access_control' => array( diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php index 93a30444139e6..e0ca4f6dedf3e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php @@ -9,7 +9,7 @@ 'main' => array( 'form_login' => true, 'remember_me' => array( - 'key' => 'TheyKey', + 'secret' => 'TheSecret', 'catch_exceptions' => false, 'token_provider' => 'token_provider_id', ), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml index 1a56aa88fda07..1e48c428000ec 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -49,14 +49,15 @@ - + + - + @@ -64,6 +65,17 @@ + + + + app.user_checker + + + + + + + ROLE_USER ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH ROLE_USER,ROLE_ADMIN diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml index 1674756891575..b6ade91a07970 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml @@ -11,7 +11,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml index 93c231ea235f1..46dd08f8ce948 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -47,7 +47,7 @@ security: stateless: true http_basic: true http_digest: - key: TheKey + secret: TheSecret form_login: true anonymous: true switch_user: true @@ -55,7 +55,9 @@ security: remote_user: true logout: true remember_me: - key: TheKey + secret: TheSecret + user_checker: ~ + host: pattern: /test host: foo\.example\.org @@ -63,6 +65,16 @@ security: anonymous: true http_basic: true + with_user_checker: + anonymous: ~ + http_basic: ~ + user_checker: app.user_checker + + simple_auth: + provider: default + anonymous: ~ + simple_form: { authenticator: simple_authenticator } + role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml index 3a38b33c521c6..a521c8c6a803d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml @@ -7,6 +7,6 @@ security: main: form_login: true remember_me: - key: TheKey + secret: TheSecret catch_exceptions: false token_provider: token_provider_id diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index 3aac20301f0f9..b70ad2a21c8c2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -45,7 +45,7 @@ public function testNoConfigForProvider() $processor = new Processor(); $configuration = new MainConfiguration(array(), array()); - $config = $processor->processConfiguration($configuration, array($config)); + $processor->processConfiguration($configuration, array($config)); } /** @@ -64,10 +64,36 @@ public function testManyConfigForProvider() $processor = new Processor(); $configuration = new MainConfiguration(array(), array()); - $config = $processor->processConfiguration($configuration, array($config)); + $processor->processConfiguration($configuration, array($config)); } public function testCsrfAliases() + { + $config = array( + 'firewalls' => array( + 'stub' => array( + 'logout' => array( + 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_id' => 'a_token_id', + ), + ), + ), + ); + $config = array_merge(static::$minimalConfig, $config); + + $processor = new Processor(); + $configuration = new MainConfiguration(array(), array()); + $processedConfig = $processor->processConfiguration($configuration, array($config)); + $this->assertArrayHasKey('csrf_token_generator', $processedConfig['firewalls']['stub']['logout']); + $this->assertEquals('a_token_generator', $processedConfig['firewalls']['stub']['logout']['csrf_token_generator']); + $this->assertArrayHasKey('csrf_token_id', $processedConfig['firewalls']['stub']['logout']); + $this->assertEquals('a_token_id', $processedConfig['firewalls']['stub']['logout']['csrf_token_id']); + } + + /** + * @group legacy + */ + public function testLegacyCsrfAliases() { $config = array( 'firewalls' => array( @@ -107,8 +133,35 @@ public function testCsrfOriginalAndAliasValueCausesException() ); $config = array_merge(static::$minimalConfig, $config); + $processor = new Processor(); + $configuration = new MainConfiguration(array(), array()); + $processor->processConfiguration($configuration, array($config)); + } + + public function testDefaultUserCheckers() + { + $processor = new Processor(); + $configuration = new MainConfiguration(array(), array()); + $processedConfig = $processor->processConfiguration($configuration, array(static::$minimalConfig)); + + $this->assertEquals('security.user_checker', $processedConfig['firewalls']['stub']['user_checker']); + } + + public function testUserCheckers() + { + $config = array( + 'firewalls' => array( + 'stub' => array( + 'user_checker' => 'app.henk_checker', + ), + ), + ); + $config = array_merge(static::$minimalConfig, $config); + $processor = new Processor(); $configuration = new MainConfiguration(array(), array()); $processedConfig = $processor->processConfiguration($configuration, array($config)); + + $this->assertEquals('app.henk_checker', $processedConfig['firewalls']['stub']['user_checker']); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php new file mode 100644 index 0000000000000..99647ec1835e6 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class GuardAuthenticationFactoryTest extends TestCase +{ + /** + * @dataProvider getValidConfigurationTests + */ + public function testAddValidConfiguration(array $inputConfig, array $expectedConfig) + { + $factory = new GuardAuthenticationFactory(); + $nodeDefinition = new ArrayNodeDefinition('guard'); + $factory->addConfiguration($nodeDefinition); + + $node = $nodeDefinition->getNode(); + $normalizedConfig = $node->normalize($inputConfig); + $finalizedConfig = $node->finalize($normalizedConfig); + + $this->assertEquals($expectedConfig, $finalizedConfig); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @dataProvider getInvalidConfigurationTests + */ + public function testAddInvalidConfiguration(array $inputConfig) + { + $factory = new GuardAuthenticationFactory(); + $nodeDefinition = new ArrayNodeDefinition('guard'); + $factory->addConfiguration($nodeDefinition); + + $node = $nodeDefinition->getNode(); + $normalizedConfig = $node->normalize($inputConfig); + // will validate and throw an exception on invalid + $node->finalize($normalizedConfig); + } + + public function getValidConfigurationTests() + { + $tests = array(); + + // completely basic + $tests[] = array( + array( + 'authenticators' => array('authenticator1', 'authenticator2'), + 'provider' => 'some_provider', + 'entry_point' => 'the_entry_point', + ), + array( + 'authenticators' => array('authenticator1', 'authenticator2'), + 'provider' => 'some_provider', + 'entry_point' => 'the_entry_point', + ), + ); + + // testing xml config fix: authenticator -> authenticators + $tests[] = array( + array( + 'authenticator' => array('authenticator1', 'authenticator2'), + ), + array( + 'authenticators' => array('authenticator1', 'authenticator2'), + 'entry_point' => null, + ), + ); + + return $tests; + } + + public function getInvalidConfigurationTests() + { + $tests = array(); + + // testing not empty + $tests[] = array( + array('authenticators' => array()), + ); + + return $tests; + } + + public function testBasicCreate() + { + // simple configuration + $config = array( + 'authenticators' => array('authenticator123'), + 'entry_point' => null, + ); + list($container, $entryPointId) = $this->executeCreate($config, null); + $this->assertEquals('authenticator123', $entryPointId); + + $providerDefinition = $container->getDefinition('security.authentication.provider.guard.my_firewall'); + $this->assertEquals(array( + 'index_0' => array(new Reference('authenticator123')), + 'index_1' => new Reference('my_user_provider'), + 'index_2' => 'my_firewall', + 'index_3' => new Reference('security.user_checker.my_firewall'), + ), $providerDefinition->getArguments()); + + $listenerDefinition = $container->getDefinition('security.authentication.listener.guard.my_firewall'); + $this->assertEquals('my_firewall', $listenerDefinition->getArgument(2)); + $this->assertEquals(array(new Reference('authenticator123')), $listenerDefinition->getArgument(3)); + } + + public function testExistingDefaultEntryPointUsed() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123'), + 'entry_point' => null, + ); + list(, $entryPointId) = $this->executeCreate($config, 'some_default_entry_point'); + $this->assertEquals('some_default_entry_point', $entryPointId); + } + + /** + * @expectedException \LogicException + */ + public function testCannotOverrideDefaultEntryPoint() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123'), + 'entry_point' => 'authenticator123', + ); + $this->executeCreate($config, 'some_default_entry_point'); + } + + /** + * @expectedException \LogicException + */ + public function testMultipleAuthenticatorsRequiresEntryPoint() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123', 'authenticatorABC'), + 'entry_point' => null, + ); + $this->executeCreate($config, null); + } + + public function testCreateWithEntryPoint() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123', 'authenticatorABC'), + 'entry_point' => 'authenticatorABC', + ); + list($container, $entryPointId) = $this->executeCreate($config, null); + $this->assertEquals('authenticatorABC', $entryPointId); + } + + private function executeCreate(array $config, $defaultEntryPointId) + { + $container = new ContainerBuilder(); + $container->register('security.authentication.provider.guard'); + $container->register('security.authentication.listener.guard'); + $id = 'my_firewall'; + $userProviderId = 'my_user_provider'; + + $factory = new GuardAuthenticationFactory(); + list($providerId, $listenerId, $entryPointId) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId); + + return array($container, $entryPointId); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 8eb2f70ea5de8..72ef2e0c3ed56 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -95,6 +95,30 @@ public function testFirewallWithInvalidUserProvider() $container->compile(); } + public function testDisableRoleHierarchyVoter() + { + $container = $this->getRawContainer(); + + $container->loadFromExtension('security', array( + 'providers' => array( + 'default' => array('id' => 'foo'), + ), + + 'role_hierarchy' => null, + + 'firewalls' => array( + 'some_firewall' => array( + 'pattern' => '/.*', + 'http_basic' => null, + ), + ), + )); + + $container->compile(); + + $this->assertFalse($container->hasDefinition('security.access.role_hierarchy_voter')); + } + protected function getRawContainer() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php index 1eccbfd795bea..dd7c19e9c7f15 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php @@ -19,7 +19,7 @@ class LoginController extends ContainerAware { public function loginAction() { - $form = $this->container->get('form.factory')->create('user_login'); + $form = $this->container->get('form.factory')->create('Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType'); return $this->container->get('templating')->renderResponse('CsrfFormLoginBundle:Login:login.html.twig', array( 'form' => $form->createView(), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginFormType.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php similarity index 76% rename from src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginFormType.php rename to src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php index ecaf9ed068839..b82e1f827897d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginFormType.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php @@ -16,7 +16,7 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; -use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Security; @@ -27,13 +27,13 @@ * @author Henrik Bjornskov * @author Jeremy Mikola */ -class UserLoginFormType extends AbstractType +class UserLoginType extends AbstractType { - private $request; + private $requestStack; - public function __construct(Request $request) + public function __construct(RequestStack $requestStack) { - $this->request = $request; + $this->requestStack = $requestStack; } /** @@ -42,12 +42,12 @@ public function __construct(Request $request) public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('username', 'text') - ->add('password', 'password') - ->add('_target_path', 'hidden') + ->add('username', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('password', 'Symfony\Component\Form\Extension\Core\Type\PasswordType') + ->add('_target_path', 'Symfony\Component\Form\Extension\Core\Type\HiddenType') ; - $request = $this->request; + $request = $this->requestStack->getCurrentRequest(); /* Note: since the Security component's form login listener intercepts * the POST request, this form will never really be bound to the @@ -76,20 +76,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) */ public function configureOptions(OptionsResolver $resolver) { - /* Note: the form's intention must correspond to that for the form login + /* Note: the form's csrf_token_id must correspond to that for the form login * listener in order for the CSRF token to validate successfully. */ $resolver->setDefaults(array( - 'intention' => 'authenticate', + 'csrf_token_id' => 'authenticate', )); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'user_login'; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index acbca59fa1bb4..5a00ac329895d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -3,12 +3,11 @@ imports: services: csrf_form_login.form.type: - class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginFormType - scope: request + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType arguments: - - '@request' + - '@request_stack' tags: - - { name: form.type, alias: user_login } + - { name: form.type } security: encoders: @@ -37,12 +36,12 @@ security: username_parameter: "user_login[username]" password_parameter: "user_login[password]" csrf_parameter: "user_login[_token]" - csrf_provider: security.csrf.token_manager + csrf_token_generator: security.csrf.token_manager anonymous: ~ logout: path: /logout_path target: / - csrf_provider: security.csrf.token_manager + csrf_token_generator: security.csrf.token_manager access_control: - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml index d3fd8d0339e86..9e5563fea5197 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutWithoutSessionInvalidation/config.yml @@ -19,7 +19,7 @@ security: require_previous_session: false remember_me: always_remember_me: true - key: key + secret: secret logout: invalidate_session: false anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml index 48fd4ed6cc3cd..60e9cb89a229a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml @@ -19,7 +19,7 @@ security: require_previous_session: false remember_me: always_remember_me: true - key: key + secret: key logout: ~ anonymous: ~ stateless: true diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 68b2c907cbf18..f588b04888161 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,28 +18,28 @@ "require": { "php": ">=5.3.9", "ext-xml": "*", - "symfony/security": "~2.7.48|~2.8.41", - "symfony/security-acl": "~2.7", - "symfony/http-kernel": "~2.7" + "symfony/security": "^2.8.41|^3.4.11", + "symfony/security-acl": "~2.7|~3.0.0", + "symfony/http-kernel": "~2.7|~3.0.0", + "symfony/polyfill-php70": "~1.0" }, "require-dev": { - "symfony/browser-kit": "~2.7", - "symfony/console": "~2.7", - "symfony/css-selector": "^2.7", - "symfony/dependency-injection": "~2.7.48|^2.8.41", - "symfony/dom-crawler": "^2.7", - "symfony/form": "~2.7.15|^2.8.8", - "symfony/framework-bundle": "~2.7.25|^2.8.18", - "symfony/http-foundation": "~2.7", - "symfony/twig-bundle": "~2.7", - "symfony/twig-bridge": "^2.7.4", - "symfony/process": "^2.7", - "symfony/validator": "~2.7", - "symfony/yaml": "^2.7", - "symfony/expression-language": "~2.7", + "symfony/browser-kit": "~2.7|~3.0.0", + "symfony/console": "~2.7|~3.0.0", + "symfony/css-selector": "^2.7|~3.0.0", + "symfony/dependency-injection": "~2.8.41", + "symfony/dom-crawler": "^2.7|~3.0.0", + "symfony/form": "^2.8.18", + "symfony/framework-bundle": "^2.8.18", + "symfony/http-foundation": "~2.7|~3.0.0", + "symfony/twig-bundle": "~2.7|~3.1.0", + "symfony/twig-bridge": "^2.7.4|~3.1.0", + "symfony/process": "^2.7|~3.0.0", + "symfony/validator": "~2.7.25|^2.8.18|~3.2.5", + "symfony/yaml": "^2.7|~3.0.0", + "symfony/expression-language": "~2.7|~3.0.0", "doctrine/doctrine-bundle": "~1.2", - "twig/twig": "~1.34|~2.4", - "ircmaxell/password-compat": "~1.0" + "twig/twig": "~1.34|~2.4" }, "autoload": { "psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" }, @@ -50,7 +50,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php index 42fc34ea4d63b..0630eebcfb9c4 100644 --- a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php @@ -11,9 +11,11 @@ namespace Symfony\Bundle\TwigBundle\CacheWarmer; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinderInterface; +use Symfony\Component\Templating\TemplateReference; use Twig\Error\Error; /** @@ -28,8 +30,12 @@ class TemplateCacheCacheWarmer implements CacheWarmerInterface { protected $container; protected $finder; + private $paths; - public function __construct(ContainerInterface $container, TemplateFinderInterface $finder = null) + /** + * @param array $paths Additional twig paths to warm + */ + public function __construct(ContainerInterface $container, TemplateFinderInterface $finder = null, array $paths = array()) { // We don't inject the Twig environment directly as it depends on the // template locator (via the loader) which might be a cached one. @@ -38,6 +44,7 @@ public function __construct(ContainerInterface $container, TemplateFinderInterfa // But it can also be null if templating has been disabled. $this->container = $container; $this->finder = $finder; + $this->paths = $paths; } /** @@ -53,7 +60,13 @@ public function warmUp($cacheDir) $twig = $this->container->get('twig'); - foreach ($this->finder->findAllTemplates() as $template) { + $templates = $this->finder->findAllTemplates(); + + foreach ($this->paths as $path => $namespace) { + $templates = array_merge($templates, $this->findTemplatesInFolder($namespace, $path)); + } + + foreach ($templates as $template) { if ('twig' !== $template->get('engine')) { continue; } @@ -75,4 +88,32 @@ public function isOptional() { return true; } + + /** + * Find templates in the given directory. + * + * @param string $namespace The namespace for these templates + * @param string $dir The folder where to look for templates + * + * @return array An array of templates of type TemplateReferenceInterface + */ + private function findTemplatesInFolder($namespace, $dir) + { + if (!is_dir($dir)) { + return array(); + } + + $templates = array(); + $finder = new Finder(); + + foreach ($finder->files()->followLinks()->in($dir) as $file) { + $name = $file->getRelativePathname(); + $templates[] = new TemplateReference( + $namespace ? sprintf('@%s/%s', $namespace, $name) : $name, + 'twig' + ); + } + + return $templates; + } } diff --git a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php new file mode 100644 index 0000000000000..9a60a92e85375 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.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\Bundle\TwigBundle\CacheWarmer; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Twig\Environment; +use Twig\Error\Error; + +/** + * Generates the Twig cache for all templates. + * + * @author Fabien Potencier + */ +class TemplateCacheWarmer implements CacheWarmerInterface +{ + private $container; + private $twig; + private $iterator; + + /** + * TemplateCacheWarmer constructor. + * + * @param ContainerInterface|Environment $container + * @param \Traversable $iterator + */ + public function __construct($container, \Traversable $iterator) + { + // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. + if ($container instanceof ContainerInterface) { + $this->container = $container; + } elseif ($container instanceof Environment) { + $this->twig = $container; + } else { + throw new \InvalidArgumentException(sprintf('%s only accepts instance of Symfony\Component\DependencyInjection\ContainerInterface or Environment as first argument.', __CLASS__)); + } + + $this->iterator = $iterator; + } + + /** + * {@inheritdoc} + */ + public function warmUp($cacheDir) + { + if (null === $this->twig) { + $this->twig = $this->container->get('twig'); + } + + foreach ($this->iterator as $template) { + try { + $this->twig->loadTemplate($template); + } catch (Error $e) { + // problem during compilation, give up + // might be a syntax error or a non-Twig template + } + } + } + + /** + * {@inheritdoc} + */ + public function isOptional() + { + return true; + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php b/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php index c4aa34098b623..444ce176ba8f8 100644 --- a/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php +++ b/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php @@ -14,6 +14,7 @@ use Symfony\Bridge\Twig\Command\DebugCommand as BaseDebugCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; @@ -57,10 +58,11 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); if (false !== strpos($input->getFirstArgument(), ':d')) { - $output->writeln('The use of "twig:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:twig" instead.'); + $io->caution('The use of "twig:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:twig" instead.'); } - parent::execute($input, $output); + parent::execute($input, $io); } } diff --git a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index 4a83c842a382b..1e2849c8039fd 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -11,12 +11,10 @@ namespace Symfony\Bundle\TwigBundle\Controller; -use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference; use Symfony\Component\HttpKernel\Exception\FlattenException; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Templating\TemplateReferenceInterface; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Loader\ExistsLoaderInterface; @@ -95,7 +93,7 @@ protected function getAndCleanOutputBuffering($startObLevel) * @param int $code An HTTP response status code * @param bool $showException * - * @return TemplateReferenceInterface + * @return string */ protected function findTemplate(Request $request, $format, $code, $showException) { @@ -106,14 +104,14 @@ protected function findTemplate(Request $request, $format, $code, $showException // For error pages, try to find a template for the specific HTTP status code and format if (!$showException) { - $template = new TemplateReference('TwigBundle', 'Exception', $name.$code, $format, 'twig'); + $template = sprintf('@Twig/Exception/%s%s.%s.twig', $name, $code, $format); if ($this->templateExists($template)) { return $template; } } // try to find a template for the given format - $template = new TemplateReference('TwigBundle', 'Exception', $name, $format, 'twig'); + $template = sprintf('@Twig/Exception/%s.%s.twig', $name, $format); if ($this->templateExists($template)) { return $template; } @@ -121,7 +119,7 @@ protected function findTemplate(Request $request, $format, $code, $showException // default to a generic HTML exception $request->setRequestFormat('html'); - return new TemplateReference('TwigBundle', 'Exception', $showException ? 'exception_full' : $name, 'html', 'twig'); + return sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : $name); } // to be removed when the minimum required version of Twig is >= 3.0 diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php index 711743a5af634..b7ebb2a406512 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php @@ -27,10 +27,6 @@ public function process(ContainerBuilder $container) return; } - if (!interface_exists('Symfony\Component\Templating\TemplateReferenceInterface')) { - $container->removeDefinition('twig.controller.exception'); - } - // register the exception controller only if Twig is enabled and required dependencies do exist if (!class_exists('Symfony\Component\Debug\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) { $container->removeDefinition('twig.exception_listener'); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index e3f700f457283..008fbd0437ed2 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -45,7 +45,14 @@ public function process(ContainerBuilder $container) if ($container->has('form.extension')) { $container->getDefinition('twig.extension.form')->addTag('twig.extension'); $reflClass = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension'); - $container->getDefinition('twig.loader.native_filesystem')->addMethodCall('addPath', array(dirname(dirname($reflClass->getFileName())).'/Resources/views/Form')); + + $coreThemePath = dirname(dirname($reflClass->getFileName())).'/Resources/views/Form'; + $container->getDefinition('twig.loader.native_filesystem')->addMethodCall('addPath', array($coreThemePath)); + + $paths = $container->getDefinition('twig.cache_warmer')->getArgument(2); + $paths[$coreThemePath] = null; + $container->getDefinition('twig.cache_warmer')->replaceArgument(2, $paths); + $container->getDefinition('twig.template_iterator')->replaceArgument(2, $paths); } if ($container->has('fragment.handler')) { diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index e0ffd0b14324b..5d8e82e7899f3 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -82,6 +83,10 @@ public function load(array $configs, ContainerBuilder $container) } } + // paths are modified in ExtensionPass if forms are enabled + $container->getDefinition('twig.cache_warmer')->replaceArgument(2, $config['paths']); + $container->getDefinition('twig.template_iterator')->replaceArgument(2, $config['paths']); + $bundleHierarchy = $this->getBundleHierarchy($container); foreach ($bundleHierarchy as $name => $bundle) { @@ -101,6 +106,7 @@ public function load(array $configs, ContainerBuilder $container) if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/views')) { $twigFilesystemLoaderDefinition->addMethodCall('addPath', array($dir)); } + $container->addResource(new FileExistenceResource($dir)); if (!empty($config['globals'])) { $def = $container->getDefinition('twig'); @@ -154,10 +160,12 @@ private function getBundleHierarchy(ContainerBuilder $container) if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/'.$name.'/views')) { $bundleHierarchy[$name]['paths'][] = $dir; } + $container->addResource(new FileExistenceResource($dir)); if (is_dir($dir = $bundle['path'].'/Resources/views')) { $bundleHierarchy[$name]['paths'][] = $dir; } + $container->addResource(new FileExistenceResource($dir)); if (null === $bundle['parent']) { continue; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index c0a15e5152ce2..11d5f3fb22d0d 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -49,6 +49,19 @@ + +
+ + + + %kernel.root_dir% + + + + + + + @@ -66,7 +79,7 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig index 074389ce919e8..c27cc56e6a078 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig @@ -1 +1 @@ -{% include 'TwigBundle:Exception:error.xml.twig' %} +{% include '@Twig/Exception/error.xml.twig' %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig index 074389ce919e8..c27cc56e6a078 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig @@ -1 +1 @@ -{% include 'TwigBundle:Exception:error.xml.twig' %} +{% include '@Twig/Exception/error.xml.twig' %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig index 989740fdd01cd..d507ce46f6949 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig @@ -1 +1 @@ -{% include 'TwigBundle:Exception:exception.xml.twig' with { 'exception': exception } %} +{% include '@Twig/Exception/exception.xml.twig' with { 'exception': exception } %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig index 870d4a0fd2407..bdf242b7f1998 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig @@ -1,3 +1,3 @@ /* -{% include 'TwigBundle:Exception:exception.txt.twig' with { 'exception': exception } %} +{% include '@Twig/Exception/exception.txt.twig' with { 'exception': exception } %} */ diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig index 4baf0844369f9..ec1679b147c7c 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig @@ -35,7 +35,7 @@ {% for position, e in exception.toarray %} - {% include 'TwigBundle:Exception:traces.html.twig' with { 'exception': e, 'position': position, 'count': previous_count } only %} + {% include '@Twig/Exception/traces.html.twig' with { 'exception': e, 'position': position, 'count': previous_count } only %} {% endfor %} {% if logger %} @@ -61,7 +61,7 @@
- {% include 'TwigBundle:Exception:logs.html.twig' with { 'logs': logger.logs } only %} + {% include '@Twig/Exception/logs.html.twig' with { 'logs': logger.logs } only %}
{% endif %} @@ -86,7 +86,7 @@ {% endif %} -{% include 'TwigBundle:Exception:traces_text.html.twig' with { 'exception': exception } only %} +{% include '@Twig/Exception/traces_text.html.twig' with { 'exception': exception } only %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/body.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/body.css.twig deleted file mode 100644 index 8373e44d0ac8d..0000000000000 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/body.css.twig +++ /dev/null @@ -1,137 +0,0 @@ -/* -Copyright (c) 2010, Yahoo! Inc. All rights reserved. -Code licensed under the BSD License: -http://developer.yahoo.com/yui/license.html -version: 3.1.2 -build: 56 -*/ -.sf-reset div,.sf-reset dl,.sf-reset dt,.sf-reset dd,.sf-reset ul,.sf-reset ol,.sf-reset li,.sf-reset h1,.sf-reset h2,.sf-reset h3,.sf-reset h4,.sf-reset h5,.sf-reset h6,.sf-reset pre,.sf-reset code,.sf-reset form,.sf-reset fieldset,.sf-reset legend,.sf-reset input,.sf-reset textarea,.sf-reset p,.sf-reset blockquote,.sf-reset th,.sf-reset td{margin:0;padding:0;}.sf-reset table{border-collapse:collapse;border-spacing:0;}.sf-reset fieldset,.sf-reset img{border:0;}.sf-reset address,.sf-reset caption,.sf-reset cite,.sf-reset code,.sf-reset dfn,.sf-reset em,.sf-reset strong,.sf-reset th,.sf-reset var{font-style:normal;font-weight:normal;}.sf-reset li{list-style:none;}.sf-reset caption,.sf-reset th{text-align:left;}.sf-reset h1,.sf-reset h2,.sf-reset h3,.sf-reset h4,.sf-reset h5,.sf-reset h6{font-size:100%;font-weight:normal;}.sf-reset q:before,.sf-reset q:after{content:'';}.sf-reset abbr,.sf-reset acronym{border:0;font-variant:normal;}.sf-reset sup{vertical-align:text-top;}.sf-reset sub{vertical-align:text-bottom;}.sf-reset input,.sf-reset textarea,.sf-reset select{font-family:inherit;font-size:inherit;font-weight:inherit;}.sf-reset input,.sf-reset textarea,.sf-reset select{font-size:100%;}.sf-reset legend{color:#000;} -.sf-reset abbr { - border-bottom: 1px dotted #000; - cursor: help; -} -.sf-reset p { - font-size: 14px; - line-height: 20px; - padding-bottom: 20px; -} -.sf-reset strong { - color: #313131; - font-weight: bold; -} -.sf-reset a { - color: #6c6159; -} -.sf-reset a img { - border: none; -} -.sf-reset a:hover { - text-decoration: underline; -} -.sf-reset em { - font-style: italic; -} -.sf-reset h2, -.sf-reset h3 { - font-weight: bold; -} -.sf-reset h1 { - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 20px; - color: #313131; - word-break: break-all; -} -.sf-reset li { - padding-bottom: 10px; -} -.sf-reset .block { - -moz-border-radius: 16px; - -webkit-border-radius: 16px; - border-radius: 16px; - margin-bottom: 20px; - background-color: #FFFFFF; - border: 1px solid #dfdfdf; - padding: 40px 50px; -} -.sf-reset h2 { - font-size: 16px; - font-family: Arial, Helvetica, sans-serif; -} -.sf-reset li a { - background: none; - color: #868686; - text-decoration: none; -} -.sf-reset li a:hover { - background: none; - color: #313131; - text-decoration: underline; -} -.sf-reset ol { - padding: 10px 0; -} -.sf-reset ol li { - list-style: decimal; - margin-left: 20px; - padding: 2px; - padding-bottom: 20px; -} -.sf-reset ol ol li { - list-style-position: inside; - margin-left: 0; - white-space: nowrap; - font-size: 12px; - padding-bottom: 0; -} -.sf-reset li .selected { - background-color: #ffd; -} -.sf-button { - display: -moz-inline-box; - display: inline-block; - text-align: center; - vertical-align: middle; - border: 0; - background: transparent none; - text-transform: uppercase; - cursor: pointer; - font: bold 11px Arial, Helvetica, sans-serif; -} -.sf-button span { - text-decoration: none; - display: block; - height: 28px; - float: left; -} -.sf-button .border-l { - text-decoration: none; - display: block; - height: 28px; - float: left; - padding: 0 0 0 7px; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQtJREFUeNpiPHnyJAMakARiByDWYEGT8ADiYGVlZStubm5xlv///4MEQYoKZGRkQkRERLRYWVl5wYJQyXBZWdkwCQkJUxAHKgaWlAHSLqKiosb//v1DsYMFKGCvoqJiDmQzwXTAJYECulxcXNLoumCSoszMzDzoumDGghQwYZUECWIzkrAkSIIGOmlkLI10AiX//P379x8jIyMTNmPf/v79+ysLCwsvuiQoNi5//fr1Kch4dAyS3P/gwYMTQBP+wxwHw0xA4gkQ73v9+vUZdJ2w1Lf82bNn4iCHCQoKasHsZw4ODgbRIL8c+/Lly5M3b978Y2dn5wC6npkFLXnsAOKLjx49AmUHLYAAAwBoQubG016R5wAAAABJRU5ErkJggg==) no-repeat top left; -} -.sf-button .border-r { - padding: 0 7px 0 0; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAR1JREFUeNpiPHnyZCMDA8MNID5gZmb2nAEJMH7//v3N169fX969e/cYkL8WqGAHXPLv37//QYzfv39/fvPmzbUnT56sAXInmJub/2H5/x8sx8DCwsIrISFhDmQyPX78+CmQXs70798/BmQsKipqBNTgdvz4cWkmkE5kDATMioqKZkCFdiwg1eiAi4tLGqhQF24nMmBmZuYEigth1QkEbEBxTlySYPvJkwSJ00AnjYylgU6gxB8g/oFVEphkvgLF32KNMmCCewYUv4qhEyj47+HDhyeBzIMYOoEp8CxQw56wsLAncJ1//vz5/P79+2svX74EJc2V4BT58+fPd8CE/QKYHMGJOiIiAp6oWW7evDkNSF8DZYfIyEiU7AAQYACJ2vxVdJW4eQAAAABJRU5ErkJggg==) right top no-repeat; -} -.sf-button .btn-bg { - padding: 0 14px; - color: #636363; - line-height: 28px; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAYAAACgXdXMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAClJREFUeNpiPnny5EKGf//+/Wf6//8/A4QAcrGzKCZwGc9sa2urBBBgAIbDUoYVp9lmAAAAAElFTkSuQmCC) repeat-x top left; -} -.sf-button:hover .border-l, -.sf-button-selected .border-l { - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAR9JREFUeNpi/P//PwMyOHfunDqQSgNiexZkibNnzxYBqZa3HOs5v7PcYQBLnjlzhg1IbfzIdsTjA/t+ht9Mr8GKwZL//v3r+sB+0OMN+zqIEf8gFMvJkyd1gXTOa9YNDP//otrPAtSV/Jp9HfPff78Z0AEL0LUeXxivMfxD0wXTqfjj/2ugkf+wSrL9/YtpJEyS4S8WI5Ek/+GR/POPFjr//cenE6/kP9q4Fo/kr39/mdj+M/zFkGQCSj5i+ccPjLJ/GBgkuYOHQR1sNDpmAkb2LBmWwL///zKCIxwZM0VHR18G6p4uxeLLAA4tJMwEshiou1iMxXaHLGswA+t/YbhORuQUv2DBAnCifvxzI+enP3dQJUFg/vz5sOzgBBBgAPxX9j0YnH4JAAAAAElFTkSuQmCC) no-repeat top left; -} -.sf-button:hover .border-r, -.sf-button-selected .border-r { - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT5JREFUeNpiPHv27BkGBoaDQDzLyMjoJgMSYHrM3WX8hn1d0f///88DFRYhSzIuv2X5H8Rg/SfKIPDTkYH/l80OINffxMTkF9O/f/8ZQPgnwyuGl+wrGd6x7vf49+9fO9jYf3+Bkkj4NesmBqAV+SdPntQC6vzHgIz//gOawbqOGchOxtAJwp8Zr4F0e7D8/fuPAR38/P8eZIo0yz8skv8YvoIk+YE6/zNgAyD7sRqLkPzzjxY6/+HS+R+fTkZ8djLh08lCUCcuSWawJGbwMTGwg7zyBatX2Bj5QZKPsBrLzaICktzN8g/NWEYGZgYZjoC/wMiei5FMpFh8QPSU6Ojoy3Cd7EwiDBJsDgxiLNY7gLrKQGIsHAxSDHxAO2TZ/b8D+TVxcXF9MCtYtLiKLgDpfUDVsxITE1GyA0CAAQA2E/N8VuHyAAAAAABJRU5ErkJggg==) right top no-repeat; -} -.sf-button:hover .btn-bg, -.sf-button-selected .btn-bg { - color: #FFFFFF; - text-shadow:0 1px 1px #6b9311; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAIAAAAvP0KbAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiPnv2LNMdvlymf///M/37B8R/QfQ/MP33L4j+B6Qh7L9//sHpf2h8MA1V+w/KRjYLaDaLCU8vQIABAFO3TxZriO4yAAAAAElFTkSuQmCC) repeat-x top left; -} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig index acc76300b59dd..d04cf37e6c7b1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig @@ -1,25 +1,14 @@ -