diff --git a/CHANGELOG-2.4.md b/CHANGELOG-2.4.md new file mode 100644 index 0000000000000..f1bbdc685396e --- /dev/null +++ b/CHANGELOG-2.4.md @@ -0,0 +1,401 @@ +CHANGELOG for 2.4.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 2.4 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.4.0...v2.4.1 + +* 2.4.9 (2014-09-03) + + * security #11832 CVE-2014-6072 (fabpot) + * security #11831 CVE-2014-5245 (stof) + * security #11830 CVE-2014-4931 (aitboudad, Jérémy Derussé) + * security #11829 CVE-2014-6061 (damz, fabpot) + * security #11828 CVE-2014-5244 (nicolas-grekas, larowlan) + * bug #10197 [FrameworkBundle] PhpExtractor bugfix and improvements (mtibben) + * bug #11772 [Filesystem] Add FTP stream wrapper context option to enable overwrite (Damian Sromek) + * bug #11788 [Yaml] fixed mapping keys containing a quoted # (hvt, fabpot) + * bug #11160 [DoctrineBridge] Abstract Doctrine Subscribers with tags (merk) + * bug #11768 [ClassLoader] Add a __call() method to XcacheClassLoader (tstoeckler) + * bug #11726 [Filesystem Component] mkdir race condition fix #11626 (kcassam) + * bug #11677 [YAML] resolve variables in inlined YAML (xabbuh) + * bug #11639 [DependencyInjection] Fixed factory service not within the ServiceReferenceGraph. (boekkooi) + * bug #11778 [Validator] Fixed wrong translations for Collection constraints (samicemalone) + * bug #11756 [DependencyInjection] fix @return anno created by PhpDumper (jakubkulhan) + * bug #11711 [DoctrineBridge] Fix empty parameter logging in the dbal logger (jakzal) + * bug #11692 [DomCrawler] check for the correct field type (xabbuh) + * bug #11672 [Routing] fix handling of nullable XML attributes (xabbuh) + * bug #11624 [DomCrawler] fix the axes handling in a bc way (xabbuh) + * bug #11676 [Form] Fixed #11675 ValueToDuplicatesTransformer accept "0" value (Nek-) + * bug #11695 [Validators] Fixed failing tests requiring ICU 52.1 which are skipped otherwise (webmozart) + * bug #11529 [WebProfilerBundle] Fixed double height of canvas (hason) + * bug #11641 [WebProfilerBundle ] Fix toolbar vertical alignment (blaugueux) + * bug #11559 [Validator] Convert objects to string in comparison validators (webmozart) + * feature #11510 [HttpFoundation] MongoDbSessionHandler supports auto expiry via configurable expiry_field (catchamonkey) + * bug #11408 [HttpFoundation] Update QUERY_STRING when overrideGlobals (yguedidi) + * bug #11633 [FrameworkBundle] add missing attribute to XSD (xabbuh) + * bug #11601 [Validator] Allow basic auth in url when using UrlValidator. (blaugueux) + * bug #11609 [Console] fixed style creation when providing an unknown tag option (fabpot) + * bug #10914 [HttpKernel] added an analyze of environment parameters for built-in server (mauchede) + * bug #11598 [Finder] Shell escape and windows support (Gordon Franke, gimler) + * bug #11499 [BrowserKit] Fixed relative redirects for ambiguous paths (pkruithof) + * bug #11516 [BrowserKit] Fix browser kit redirect with ports (dakota) + * bug #11545 [Bundle][FrameworkBundle] built-in server: exit when docroot does not exist (xabbuh) + * bug #11560 Plural fix (1emming) + * bug #11558 [DependencyInjection] Fixed missing 'factory-class' attribute in XmlDumper output (kerdany) + * bug #11548 [Component][DomCrawler] fix axes handling in Crawler::filterXPath() (xabbuh) + * bug #11422 [DependencyInjection] Self-referenced 'service_container' service breaks garbage collection (sun) + * bug #11428 [Serializer] properly handle null data when denormalizing (xabbuh) + * bug #10687 [Validator] Fixed string conversion in constraint violations (eagleoneraptor, webmozart) + * bug #11475 [EventDispatcher] don't count empty listeners (xabbuh) + * bug #11436 fix signal handling in wait() on calls to stop() (xabbuh, romainneutron) + * bug #11469 [BrowserKit] Fixed server HTTP_HOST port uri conversion (bcremer, fabpot) + * bug #11425 Fix issue described in #11421 (Ben, ben-rosio) + * bug #11423 Pass a Scope instance instead of a scope name when cloning a container in the GrahpvizDumper (jakzal) + * bug #11448 [MonologBridge] fixed Console handler priorities (fabpot) + * bug #11120 [Process] Reduce I/O load on Windows platform (romainneutron) + * bug #11342 [Form] Check if IntlDateFormatter constructor returned a valid object before using it (romainneutron) + * bug #11439 [ExpressionLanguage] Fixed double quoted string literals containing sharps (pylebecq) + * bug #11411 [Validator] Backported #11410 to 2.3: Object initializers are called only once per object (webmozart) + * bug #11403 [Translator][FrameworkBundle] Added @ to the list of allowed chars in Translator (takeit) + * bug #11381 [Process] Use correct test for empty string in UnixPipes (whs, romainneutron) + +* 2.4.8 (2014-07-15) + + * [Security] Forced validate of locales passed to the translator + * bug #11278 Remove Spaceless Blocks From Twig Templates (chrisguitarguy) + * feature #11367 [HttpFoundation] Fix to prevent magic bytes injection in JSONP responses... (CVE-2014-4671) (Andrew Moore) + * bug #11386 Remove Spaceless Blocks from Twig Form Templates (chrisguitarguy) + * bug #9719 [TwigBundle] fix configuration tree for paths (mdavis1982, cordoval) + * bug #11244 [HttpFoundation] Remove body-related headers when sending the response, if body is empty (SimonSimCity) + +* 2.4.7 (2014-07-08) + + * bug #11283 [SecurityBundle] Remove Expression Language services when the component is unavailable (thewilkybarkid) + * bug #11238 [Translation] Added unescaping of ids in PoFileLoader (JustBlackBird) + * bug #11194 [DomCrawler] Remove the query string and the anchor of the uri of a link (benja-M-1) + * bug #11272 [Console] Make sure formatter is the same. (akimsko) + * bug #11259 [Config] Fixed failed config schema loads due to libxml_disable_entity_loader usage (ccorliss) + * bug #11234 [ClassLoader] fixed PHP warning on PHP 5.3 (fabpot) + * bug #11179 [Process] Fix ExecutableFinder with open basedir (cs278) + * bug #11242 [CssSelector] Refactored the CssSelector to remove the circular object graph (stof) + * bug #11219 [DomCrawler] properly handle buttons with single and double quotes insid... (xabbuh) + * bug #11220 [Components][Serializer] optional constructor arguments can be omitted during the denormalization process (xabbuh) + * bug #11186 Added missing `break` statement (apfelbox) + * bug #11169 [Console] Fixed notice in DialogHelper (florianv) + * bug #11144 [HttpFoundation] Fixed Request::getPort returns incorrect value under IPv6 (kicken) + * bug #10966 PHP Fatal error when getContainer method of ContainerAwareCommand has be... (kevinvergauwen) + * bug #10981 [HttpFoundation] Fixed isSecure() check to be compliant with the docs (Jannik Zschiesche) + * bug #11092 [HttpFoundation] Fix basic authentication in url with PHP-FPM (Kdecherf) + * bug #10808 [DomCrawler] Empty select with attribute name="foo[]" bug fix (darles) + * bug #11063 [HttpFoundation] fix switch statement (Tobion) + * bug #11009 [HttpFoundation] smaller fixes for PdoSessionHandler (Tobion) + * bug #11041 Remove undefined variable $e (skydiablo) + +* 2.4.6 (2014-05-31) + + * bug #11014 [Validator] Remove property and method targets from the optional and required constraints (jakzal) + * bug #10983 [DomCrawler] Fixed charset detection in html5 meta charset tag (77web) + * Merge branch '2.3' into 2.4 + * bug #10979 Make rootPath part of regex greedy (artursvonda) + * bug #10995 [TwigBridge][Trans]set %count% only on transChoice from the current context. (aitboudad) + * bug #10987 [DomCrawler] Fixed a forgotten case of complex XPath queries (stof) + +* 2.4.5 (2014-05-22) + + * bug #10849 [WIP][Finder] Fix wrong implementation on sortable callback comparator (ProPheT777) + * bug #10929 [Process] Add validation on Process input (romainneutron) + * bug #10958 [DomCrawler] Fixed filterXPath() chaining loosing the parent DOM nodes (stof, robbertkl) + * bug #10953 [HttpKernel] fixed file uploads in functional tests without file selected (realmfoo) + * bug #10947 [PropertyAccess] Fixed getValue() when accessing non-existing indices of ArrayAccess implementations (webmozart) + * bug #10937 [HttpKernel] Fix "absolute path" when we look to the cache directory (BenoitLeveque) + * bug #10894 [HttpKernel] removed absolute paths from the generated container (fabpot) + * bug #10926 [DomCrawler] Fixed the initial state for options without value attribute (stof) + * bug #10925 [DomCrawler] Fixed the handling of boolean attributes in ChoiceFormField (stof) + * bug #10777 [Form] Automatically add step attribute to HTML5 time widgets to display seconds if needed (tucksaun) + * bug #10909 [PropertyAccess] Fixed plurals for -ves words (csarrazi) + * bug #10904 [HttpKernel] Replace sha1 with sha256 in recently added tests (jakzal) + * bug #10899 Explicitly define the encoding. (jakzal) + * bug #10897 [Console] Fix a console test (jakzal) + * bug #10896 [HttpKernel] Fixed cache behavior when TTL has expired and a default "global" TTL is defined (alquerci, fabpot) + * bug #10841 [DomCrawler] Fixed image input case sensitive (geoffrey-brier) + * bug #10714 [Console]Improve formatter for double-width character (denkiryokuhatsuden) + * bug #10872 [Form] Fixed TrimListenerTest as of PHP 5.5 (webmozart) + * bug #10762 [BrowserKit] Allow URLs that don't contain a path when creating a cookie from a string (thewilkybarkid) + * bug #10863 [Security] Add check for supported attributes in AclVoter (artursvonda) + * bug #10833 [TwigBridge][Transchoice] set %count% from the current context. (aitboudad) + * bug #10820 [WebProfilerBundle] Fixed profiler seach/homepage with empty token (tucksaun) + * bug #10815 Fixed issue #5427 (umpirsky) + * bug #10817 [Debug] fix #10313: FlattenException not found (nicolas-grekas) + * bug #10803 [Debug] fix ErrorHandlerTest when context is not an array (nicolas-grekas) + * bug #10801 [Debug] ErrorHandler: remove $GLOBALS from context in PHP5.3 fix #10292 (nicolas-grekas) + * bug #10797 [HttpFoundation] Allow File instance to be passed to BinaryFileResponse (anlutro) + * bug #10643 [TwigBridge] Removed strict check when found variables inside a translation (goetas) + * bug #10605 [ExpressionLanguage] Strict in_array check in Parser.php (parnas) + +* 2.4.4 (2014-04-27) + + * bug #10789 [Console] Fixed the rendering of exceptions on HHVM with a terminal width (stof) + * bug #10773 [WebProfilerBundle ] Fixed an edge case on WDT loading (tucksaun) + * bug #10784 [Security] removed $csrfTokenManager type hint from SimpleFormAuthenticationListener constructor argument (choonge) + * bug #10763 [Process] Disable TTY mode on Windows platform (romainneutron) + * bug #10772 [Finder] Fix ignoring of unreadable dirs in the RecursiveDirectoryIterator (jakzal) + * bug #10757 [Process] Setting STDIN while running should not be possible (romainneutron) + * bug #10749 Fixed incompatibility of x509 auth with nginx (alcaeus) + * bug #10735 [Translation] [PluralizationRules] Little correction for case 'ar' (klyk50) + * bug #10720 [HttpFoundation] Fix DbalSessionHandler (Tobion) + * bug #10721 [HttpFoundation] status 201 is allowed to have a body (Tobion) + * bug #10728 [Process] Fix #10681, process are failing on Windows Server 2003 (romainneutron) + * bug #10733 [DomCrawler] Textarea value should default to empty string instead of null. (Berdir) + * bug #10723 [Security] fix DBAL connection typehint (Tobion) + * bug #10715 [Debug] Fixed ClassNotFoundFatalErrorHandler on windows. (lyrixx) + * bug #10700 Fixes various inconsistencies in the code (fabpot) + * bug #10697 [Translation] Make IcuDatFileLoader/IcuResFileLoader::load invalid resource compatible with HHVM. (idn2104) + * bug #10652 [HttpFoundation] fix PDO session handler under high concurrency (Tobion) + * bug #10690 [Validator] Fix hack for nested Collection/All losing context (GromNaN) + * bug #10669 [Profiler] Prevent throwing fatal errors when searching timestamps or invalid dates (stloyd) + * bug #10670 [Templating] PhpEngine should propagate charset to its helpers (stloyd) + * bug #10665 [DependencyInjection] Fix ticket #10663 - Added setCharset method call to PHP templating engine (koku) + * bug #10654 Changed the typehint of the EsiFragmentRenderer to the interface (stof) + * bug #10649 [BrowserKit] Fix #10641 : BrowserKit is broken when using ip as host (romainneutron) + +* 2.4.3 (2014-04-04) + + * bug #10586 Fixes URL validator to accept single part urls (merk) + * bug #10591 [Form] Buttons are now disabled if their containing form is disabled (webmozart) + * bug #10587 [Process] Fix Process test suite (romainneutron) + * bug #10579 HHVM fixes (fabpot) + * bug #10564 fixed the profiler when an uncalled listener throws an exception when instantiated (fabpot) + * bug #10568 [Form] Fixed hashing of choice lists containing non-UTF-8 characters (webmozart) + * bug #10536 Avoid levenshtein comparison when using ContainerBuilder. (catch56) + * bug #10549 Fixed server values in BrowserKit (fabpot) + * bug #10540 [HttpKernel] made parsing controllers more robust (fabpot) + * bug #10545 [DependencyInjection] Fixed YamlFileLoader imports path (jrnickell) + * bug #10523 [Debug] Check headers sent before sending PHP response (GromNaN) + * bug #10363 [FrameworkBundle][Console] Fix issue #10345 container:debug --parameter="" not working anymore (FineWolf) + * bug #10275 [Validator] Fixed ACE domain checks on UrlValidator (#10031) (aeoris) + * bug #10123 handle array root element (greg0ire) + * bug #10532 Fixed regression when using Symfony on filesystems without chmod support (fabpot) + * bug #10502 [HttpKernel] Fix #10437: Catch exceptions when reloading a no-cache request (romainneutron) + * bug #10493 Fix libxml_use_internal_errors and libxml_disable_entity_loader usage (romainneutron) + * bug #9784 [HttpFoundation] Removed ini check to make Uploadedfile work on Google App Engine (micheleorselli) + * bug #10416 [Form] Allow options to be grouped by objects (felds) + * bug #10410 [Form] Fix "Array was modified outside object" in ResizeFormListener. (Chekote) + * bug #10494 [Validator] Minor fix in IBAN validator (sprain) + * bug #10491 Fixed bug that incorrectly causes the "required" attribute to be omitted from select even though it contains the "multiple" attribute (fabpot) + * bug #10479 [Process] Fix escaping on Windows (romainneutron) + * bug #10480 [Process] Fixed fatal errors in getOutput and getErrorOutput when process was not started (romainneutron) + * bug #10420 [Process] Make Process::start non-blocking on Windows platform (romainneutron) + * bug #10455 [Process] Fix random failures in test suite on TravisCI (romainneutron) + * bug #10448 [Process] Fix quoted arguments escaping (romainneutron) + * bug #10444 [DomCrawler] Fixed incorrect value name conversion in getPhpValues() and getPhpFiles() (romainneutron) + * bug #10423 [Config] XmlUtils::convertDomElementToArray does not handle '0' (bendavies) + * bug #10153 [Process] Fixed data in pipe being truncated if not read before process termination (astephens25) + * bug #10429 [Process] Fix #9160 : escaping an argument with a trailing backslash on windows fails (romainneutron) + * bug #10412 [Process] Fix process status in TTY mode (romainneutron) + * bug #10382 10158 get vary multiple (bbinkovitz) + * bug #10251 [Form] Fixes empty file-inputs getting treated as extra field. (jenkoian) + * bug #10284 [HttpKernel] Fix issue #10209 (stephpy) + * bug #10351 [HttpKernel] fix stripComments() normalizing new-lines (sstok) + * bug #10348 Update FileLoader to fix issue #10339 (msumme) + * bug #10306 [Serializer] Throw exception when unable to normalize embedded object (gquemener) + * bug #10338 [ExpressionLanguage] Fixed evaluation of short circuit operators (florianv) + * bug #10146 [WebProfilerBundle] fixed parsing Mongo DSN and added Test for it (malarzm) + * bug #10299 [Finder] () is also a valid delimiter (WouterJ) + * bug #10255 [FrameworkBundle] Fixed wrong redirect url if path contains some query parameters (pulzarraider) + * bug #10285 Bypass sigchild detection if phpinfo is not available (Seldaek) + * bug #10269 [Form] Revert "Fix "Array was modified outside object" in ResizeFormListener." (norzechowicz) + * bug #10231 [Console] removed problematic regex (fabpot) + * bug #10245 [DomCrawler] Added support for tags to be treated as links (shamess) + * bug #10232 [Form] Fix "Array was modified outside object" in ResizeFormListener. (Chekote) + +* 2.4.2 (2014-02-12) + + * bug #10215 [Routing] reduced recursion in dumper (arnaud-lb) + * bug #10207 [DomCrawler] Fixed filterXPath() chaining (robbertkl) + * bug #10205 [DomCrawler] Fixed incorrect handling of image inputs (robbertkl) + * bug #10191 [HttpKernel] fixed wrong reference in TraceableEventDispatcher (fabpot) + * bug #10195 [Debug] Fixed recursion level incrementing in FlattenException::flattenArgs(). (sun) + * bug #10151 [Form] Update DateTime objects only if the actual value has changed (peterrehm) + * bug #10140 allow the TextAreaFormField to be used with valid/invalid HTML (dawehner) + * bug #10131 added lines to exceptions for the trans and transchoice tags (fabpot) + * bug #10002 Routing condition bugfix (marco-jantke) + * bug #10119 [Validator] Minor fix in XmlFileLoader (florianv) + * bug #10078 [BrowserKit] add non-standard port to HTTP_HOST server param (kbond) + * bug #10095 [Security] fix DI for SimpleFormAuthenticationListener (Tobion) + * bug #10091 [Translation] Update PluralizationRules.php (guilhermeblanco) + * bug #10053 [Form] fixed allow render 0 numeric input value (dczech) + * bug #10067 [HttpKernel] allow null value in fragment handler (kbond) + * bug #10042 [Expression Language] fix foo[index] (schokocappucino) + * bug #10033 [HttpKernel] Bugfix - Logger Deprecation Notice (Rican7) + * bug #10023 [FrameworkBundle] Thrown an HttpException instead returning a Response in RedirectController::redirectAction() (jakzal) + * bug #9985 Prevent WDT from creating a session (mvrhov) + * bug #10010 [Twig Bridge] Fixed bug in ExpressionExtension (ricbra) + * bug #10000 [Console] Fixed the compatibility with HHVM (stof) + * bug #9979 [Doctrine Bridge][Validator] Fix for null values in assosiated properties when using UniqueEntityValidator (vpetrovych) + * bug #9983 [TwigBridge] Update min. version of Twig (stloyd) + * bug #9970 [CssSelector] fixed numeric attribute issue (jfsimon) + * bug #9747 [DoctrineBridge] Fix: Add type detection. Needed by pdo_dblib (iamluc) + * bug #9962 [Process] Fix #9861 : Revert TTY mode (romainneutron) + * bug #9960 [Form] Update minimal requirement in composer.json (stloyd) + * bug #9952 [Translator] Fix Empty translations with Qt files (vlefort) + * bug #9957 [Console] Fixed command name guessing if an alternative is an alias (jakzal) + * bug #9948 [WebProfilerBundle] Fixed profiler toolbar icons for XHTML. (rafalwrzeszcz) + * bug #9933 Propel1 exception message (jaugustin) + * bug #9949 [BrowserKit] Throw exception on invalid cookie expiration timestamp (anlutro) + +* 2.4.1 (2014-01-05) + + * bug #9938 [Process] Add support SAPI cli-server (peter-gribanov) + * bug #9940 [EventDispatcher] Fix hardcoded listenerTag name in error message (lemoinem) + * bug #9923 [DoctrineBridge] Fixed an issue with DoctrineParserCache (florianv) + * bug #9908 [HttpFoundation] Throw proper exception when invalid data is passed to JsonResponse class (stloyd) + * bug #9902 [Security] fixed pre/post authentication checks (fabpot) + * bug #9910 fixed missing use statements (fabpot) + * bug #9895 [Intl] Added round support for ROUND_CEILING, ROUND_FLOOR, ROUND_DOWN, ROUND_UP (pamil) + * bug #9899 [Filesystem | WCM] 9339 fix stat on url for filesystem copy (cordoval) + * bug #9589 [DependencyInjection] Fixed #9020 - Added support for collections in service#parameters (lavoiesl) + * bug #9889 [Console] fixed column width when using the Table helper with some decoration in cells (fabpot) + * bug #9323 [DomCrawler]fix #9321 Crawler::addHtmlContent add gbk encoding support (bronze1man) + * bug #8997 [Security] Fixed problem with losing ROLE_PREVIOUS_ADMIN role. (pawaclawczyk) + * bug #9557 [DoctrineBridge] Fix for cache-key conflict when having a \Traversable as choices (DRvanR) + * bug #9879 [Security] Fix ExceptionListener to catch correctly AccessDeniedException if is not first exception (fabpot) + * bug #9885 [Dependencyinjection] Fixed handling of inlined references in the AnalyzeServiceReferencesPass (fabpot) + * bug #9884 [DomCrawler] Fixed creating form objects from named form nodes (jakzal) + * bug #9882 Add support for HHVM in the getting of the PHP executable (fabpot) + * bug #9850 [Validator] Fixed IBAN validator with 0750447346 value (stewe) + * bug #9865 [Validator] Fixes message value for objects (jongotlin) + * bug #9441 [Form][DateTimeToArrayTransformer] Check for hour, minute & second validity (egeloen) + * bug #9720 [FrameworkBundle] avoid tables to have apparently long blank line breaks and be too far appart for long nested array params (cordoval) + * bug #9867 #9866 [Filesystem] Fixed mirror for symlinks (COil) + * bug #9806 [Security] Fix parent serialization of user object (ddeboer) + * bug #9834 [DependencyInjection] Fixed support for backslashes in service ids. (jakzal) + * bug #9826 fix #9356 [Security] Logger should manipulate the user reloaded from provider (matthieuauger) + * feature #9775 [FrameworkBundle] Added extra details in XMLDescriptor to improve container description (Exelenz) + * bug #9771 Crawler default namespace fix (crudecki) + * bug #9769 [BrowserKit] fixes #8311 CookieJar is totally ignorant of RFC 6265 edge cases (jzawadzki) + * bug #9697 [Config] fix 5528 let ArrayNode::normalizeValue respect order of value array provided (cordoval) + * bug #9701 [Config] fix #7243 allow 0 as arraynode name (cordoval) + * bug #9795 [Form] Fixed issue in BaseDateTimeTransformer when invalid timezone cause Trans... (tyomo4ka) + * bug #9714 [HttpFoundation] BinaryFileResponse should also return 416 or 200 on some range-requets (SimonSimCity) + * bug #9601 [Routing] Remove usage of deprecated _scheme requirement (Danez) + * bug #9489 [DependencyInjection] Add normalization to tag options (WouterJ) + * bug #9135 [Form] [Validator] fix maxLength guesser (franek) + * bug #9790 [Filesystem] Changed the mode for a target file in copy() to be write only (jakzal) + * bug #9758 [Console] fixed TableHelper when cell value has new line (k-przybyszewski) + * bug #9760 [Routing] Fix router matching pattern against multiple hosts (karolsojko) + * bug #9768 [FrameworkBundle] Fixed bug in XMLDescriptor (Exelenz) + * bug #9700 [ExpressionLanguage] throw exception when parameters contain expressions (aitboudad) + * bug #9674 [Form] rename validators.ua.xlf to validators.uk.xlf (craue) + * bug #9722 [Validator]Fixed getting wrong msg when value is an object in Exception (aitboudad) + * bug #9750 allow TraceableEventDispatcher to reuse event instance in nested events (evillemez) + * bug #9718 [validator] throw an exception if isn't an instance of ConstraintValidatorInterface. (aitboudad) + * bug #9716 Reset the box model to content-box in the web debug toolbar (stof) + * bug #9711 [FrameworkBundle] Allowed "0" as a checkbox value in php templates (jakzal) + +* 2.4.0 (2013-12-03) + + * bug #9673 Fixed BC break in csrf protection (WouterJ) + * bug #9665 [Bridge/Doctrine] ORMQueryBuilderLoader - handled the scenario when no entity manager is passed with closure query builder (jakzal) + * bug #9662 [FrameworkBundle] Enabled csrf_protection by default if form.csrf_protection is enabled (bschussek) + * bug #9656 [DoctrineBridge] normalized class names in the ORM type guesser (fabpot) + * bug #9647 use the correct class name to retrieve mapped class' metadata and reposi... (xabbuh) + * bug #9648 [Debug] ensured that a fatal PHP error is actually fatal after being handled by our error handler (fabpot) + * bug #9643 [WebProfilerBundle] Fixed js escaping in time.html.twig (hason) + * bug #9641 [Debug] Avoid notice from being "eaten" by fatal error. (fabpot) + * bug #9639 Modified guessDefaultEscapingStrategy to not escape txt templates (fabpot) + * bug #9314 [Form] Fix DateType for 32bits computers. (WedgeSama) + * bug #9443 [FrameworkBundle] Fixed the registration of validation.xml file when the form is disabled (hason) + * bug #9625 [HttpFoundation] Do not return an empty session id if the session was closed (Taluu) + * bug #9621 [ExpressionLanguage] fixed lexing expression ending with spaces (fabpot) + * bug #9637 [Validator] Replaced inexistent interface (jakzal) + * bug #9628 [HttpKernel] Fix profiler event-listener usage outside request stack context (romainneutron) + * bug #9624 [Console] Fix undefined offset when formatting namespace suggestions (GromNaN) + * bug #9605 Adjusting CacheClear Warmup method to namespaced kernels (rdohms) + * bug #9617 [HttpKernel] Http kernel regression fix (hhamon) + * bug #9610 Container::camelize also takes backslashes into consideration (ondrejmirtes) + +* 2.4.0-RC1 (2013-11-25) + + * bug #9607 [HttpKernel] Fix a bug when using the kernel property in overridden method Client::setServerParameters() (gnutix) + * bug #9597 [Security] Typos in Security's ExpressionLanguage (ovrflo) + * feature #9587 [SecurityBundle] Added csrf_token_generator and csrf_token_id as new (shieldo) + * feature #9578 [DomCrawler] Fixes `attr` method returning empty string for missing attributes (aik099) + * bug #9565 [Debug] Fixed ClassNotFoundFatalErrorHandler which could cause a fatal error (jakzal) + * bug #9525 Cache Warmup Breaks Namespaced Kernel (rdohms) + * bug #9447 [BrowserKit] fixed protocol-relative url redirection (jong99) + * bug #9535 No Entity Manager defined exception (armetiz) + * bug #9485 [Acl] Fix for issue #9433 (guilro) + * bug #9516 [AclProvider] Fix incorrect behavior when partial results returned from cache (superdav42) + * feature #9546 [FrameworkBundle] use the new request_stack service in the GlobalVariables object (hhamon) + * bug #9566 [Console] Revert BC-break for verbose option value (chEbba) + * bug #9553 [FrameworkBundle] use the new request_stack service to get the Request object in the base Controller class (hhamon) + * feature #9541 [Translation] make IdentityTranslater consistent with normal translator (Tobion) + * bug #9536 [FrameworkBundle] Update 2 dependencies (currently broken) (asm89) + * bug #9352 [Intl] make currency bundle merge fallback locales when accessing data, ... (shieldo) + * bug #9537 [FrameworkBundle] Fix mistake in translation's service definition. (phpmike) + * bug #9529 [ExpressionLanguage] Fixed conflict between punctation and range (WouterJ) + * bug #9367 [Process] Check if the pipe array is empty before calling stream_select() (jfposton) + * bug #9211 [Form] Fixed memory leak in FormValidator (bschussek) + * bug #9469 [Propel1] re-factor Propel1 ModelChoiceList (havvg) + * bug #9499 Request::overrideGlobals() may call invalid ini value (denkiryokuhatsuden) + * feature #9494 made Router implement RequestMatcherInterface (fabpot) + * bug #9420 [Console][ProgressHelper] Fix ProgressHelper redraw when redrawFreq is greater than 1 (giosh94mhz) + * bug #9212 [Validator] Force Luhn Validator to only work with strings (Richtermeister) + * bug #9476 Fixed bug with lazy services (peterrehm) + * bug #9461 set mergeFallback to true, (ychadwick) + * bug #9451 Fix bug with variable named context to securityContext (mieszko4) + * feature #9434 moved logic for the session listeners into the HttpKernel component (fabpot) + * bug #9431 [DependencyInjection] fixed YamlDumper did not make services private. (realityking) + * bug #9332 [Config] Quoting reserved characters (WouterJ) + * bug #9416 fixed issue with clone now the children of the original form are preserved and the clone form is given new children (yjv) + * bug #9423 [Form] fix CsrfProviderAdapter (Tobion) + * feature #9342 Add X-Debug-Url profiler url header (adrienbrault) + * bug #9412 [HttpFoundation] added content length header to BinaryFileResponse (kbond) + +* 2.4.0-BETA2 (2013-10-30) + + * bug #9408 [Form] Fixed failing FormDataExtractorTest (bschussek) + * bug #9397 [BUG][Form] Fix nonexistent key id in twig of data collector (francoispluchino) + * bug #9395 [HttpKernel] fixed memory limit display in MemoryDataCollector (hhamon) + * bug #9168 [FrameworkBundle] made sure that the debug event dispatcher is used everywhere (fabpot) + * bug #9388 [Form] Fixed: The "data" option is taken into account even if it is NULL (bschussek) + * bug #9394 [Form] Fixed form debugger to work even when no view variables are logged (bschussek) + * bug #9391 [Serializer] Fixed the error handling when decoding invalid XML to avoid a Warning (stof) + * feature #9365 prevent PHP from magically setting a 302 header (lsmith77) + * feature #9252 [FrameworkBundle] Only enable CSRF protection when enabled in config (asm89) + * bug #9378 [DomCrawler] [HttpFoundation] Make `Content-Type` attributes identification case-insensitive (matthieuprat) + * bug #9354 [Process] Fix #9343 : revert file handle usage on Windows platform (romainneutron) + * bug #9335 [Form] Improved FormTypeCsrfExtension to use the type class as default intention if the form name is empty (bschussek) + * bug #9334 [Form] Improved FormTypeCsrfExtension to use the type class as default intention if the form name is empty (bschussek) + * bug #9333 [Form] Improved FormTypeCsrfExtension to use the type class as default intention if the form name is empty (bschussek) + * bug #9338 [DoctrineBridge] Added type check to prevent calling clear() on arrays (bschussek) + * bug #9330 [Config] Fixed namespace when dumping reference (WouterJ) + * bug #9329 [Form] Changed FormTypeCsrfExtension to use the form's name as default token ID (bschussek) + * bug #9328 [Form] Changed FormTypeCsrfExtension to use the form's name as default intention (bschussek) + * bug #9327 [Form] Changed FormTypeCsrfExtension to use the form's name as default intention (bschussek) + * bug #9316 [WebProfilerBundle] Fixed invalid condition in form panel (bschussek) + * bug #9308 [DoctrineBridge] Loosened CollectionToArrayTransformer::transform() to accept arrays (bschussek) + * bug #9297 [Form] Add missing use in form renderer (egeloen) + * bug #9309 [Routing] Fixed unresolved class (francoispluchino) + * bug #9274 [Yaml] Fixed the escaping of strings starting with a dash when dumping (stof) + * bug #9270 [Templating] Fix in ChainLoader.php (janschoenherr) + * bug #9246 [Session] fixed wrong started state (tecbot) + * bug #9234 [Debug] Fixed `ClassNotFoundFatalErrorHandler` (tPl0ch) + * bug #9259 [Process] Fix latest merge from 2.2 in 2.3 (romainneutron) + * bug #9237 [FrameworkBundle] assets:install command should mirror .dotfiles (.htaccess) (FineWolf) + * bug #9223 [Translator] PoFileDumper - PO headers (Padam87) + * bug #9257 [Process] Fix 9182 : random failure on pipes tests (romainneutron) + * bug #9236 [Form] fix missing use statement for exception UnexpectedTypeException (jaugustin) + * bug #9222 [Bridge] [Propel1] Fixed guessed relations (ClementGautier) + * bug #9214 [FramworkBundle] Check event listener services are not abstract (lyrixx) + * bug #9207 [HttpKernel] Check for lock existence before unlinking (ollietb) + * bug #9184 Fixed cache warmup of paths which contain back-slashes (fabpot) + * bug #9192 [Form] remove MinCount and MaxCount constraints in ValidatorTypeGuesser (franek) + +* 2.4.0-BETA1 (2013-10-07) + + * first beta release + diff --git a/CHANGELOG-2.5.md b/CHANGELOG-2.5.md new file mode 100644 index 0000000000000..ad58ea619ca47 --- /dev/null +++ b/CHANGELOG-2.5.md @@ -0,0 +1,301 @@ +CHANGELOG for 2.5.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 2.5 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.5.0...v2.5.1 + +* 2.5.8 (2014-12-03) + + * bug #12811 Configure firewall's kernel exception listener with configured entry point or a default entry point (rjkip) + * bug #12784 [DependencyInjection] make paths relative to __DIR__ in the generated container (nicolas-grekas) + * bug #12716 [ClassLoader] define constant only if it wasn't defined before (xabbuh) + * bug #12553 [Debug] fix error message on double exception (nicolas-grekas) + * bug #12550 [FrameworkBundle] backport #12489 (xabbuh) + * bug #12570 Fix initialized() with aliased services (Daniel Wehner) + * bug #12267 [Form][WebProfiler] Empty form names fix (kix) + * bug #12137 [FrameworkBundle] cache:clear command fills *.php.meta files with wrong data (Strate) + +* 2.5.7 (2014-11-20) + + * bug #12525 [Bundle][FrameworkBundle] be smarter when guessing the document root (xabbuh) + * bug #12296 [SecurityBundle] Authentication entry point is only registered with firewall exception listener, not with authentication listeners (rjkip) + * bug #12489 [FrameworkBundle] Fix server run in case the router script does not exist (romainneutron) + * bug #12443 [HttpKernel] Adding support for invokable controllers in the RequestDataCollector (jameshalsall) + * bug #12393 [DependencyInjection] inlined factory not referenced (boekkooi) + * bug #12436 [Filesystem] Fixed case for empty folder (yosmanyga) + * bug #12397 [Routing] fix BC (nicolas-grekas) + * bug #12382 [Routing] removed errors from git (HeinZawHtet) + * bug #12370 [Yaml] improve error message for multiple documents (xabbuh) + * bug #12170 [Form] fix form handling with OPTIONS request method (Tobion) + * bug #12235 [Validator] Fixed Regex::getHtmlPattern() to work with complex and negated patterns (webmozart) + * bug #12326 [Session] remove invalid hack in session regenerate (Tobion) + * bug #12341 [Kernel] ensure session is saved before sending response (Tobion) + * bug #12329 [Routing] serialize the compiled route to speed things up (Tobion) + * bug #12291 [Form] Fixed usage of "name" variable in form_start block (webmozart) + * bug #12316 Break infinite loop while resolving aliases (chx) + * bug #12313 [Security][listener] change priority of switchuser (aitboudad) + +* 2.5.6 (2014-10-24) + + * bug #11696 [Form] Fix #11694 - Enforce options value type check in some form types (kix) + * bug #12225 [SecurityBundle] Add trust_resolver variable into expression (zulus) + * bug #12209 [FrameworkBundle] Fixed ide links (hason) + * bug #12208 Add missing argument (WouterJ) + * bug #12197 [TwigBundle] do not pass a template reference to twig (Tobion) + * bug #12196 [TwigBundle] show correct fallback exception template in debug mode (Tobion) + * bug #12187 [CssSelector] don't raise warnings when exception is thrown (xabbuh) + * bug #12151 [Framework][DX] Set the proper validator class according to the configured api version (peterrehm) + * bug #12128 [Console] clean handling of :: passed to find() (xabbuh) + * bug #11998 [Intl] Integrated ICU data into Intl component #2 (webmozart) + * bug #11920 [Intl] Integrated ICU data into Intl component #1 (webmozart) + +* 2.5.5 (2014-09-28) + + * bug #12016 [Validator] Added ConstraintValidator::buildViolation() helper for BC with the 2.4 API (webmozart) + * bug #12031 [Validator] Fixed LegacyValidator when only a constraint is validated (webmozart) + * bug #9453 [Form][DateTime] Propagate invalid_message & invalid_message_parameters to date & time (egeloen) + * bug #12030 Fix expression language in the container when using the "container" variable (fabpot) + * bug #12032 [Command] Set the process title as late as possible (lyrixx) + * bug #11058 [Security] bug #10242 Missing checkPreAuth from RememberMeAuthenticationProvider (glutamatt) + * bug #12004 [Form] Fixed ValidatorTypeGuesser to guess properties without constraints not to be required (webmozart) + * bug #11904 Make twig ExceptionController conformed with ExceptionListener (megazoll) + * bug #11924 [Form] Moved POST_MAX_SIZE validation from FormValidator to request handler (rpg600, webmozart) + * bug #12002 [Security] [Firewall] Bug fixed in SimplePreAuthenticationListener when createToken() not return TokenInterface object (adenkejawen, fabpot) + * bug #11079 Response::isNotModified returns true when If-Modified-Since is later than Last-Modified (skolodyazhnyy) + * bug #11989 [Finder][Urgent] Remove asterisk and question mark from folder name in test to prevent windows file system issues. (Adam) + * bug #11908 [Translation] [Config] Clear libxml errors after parsing xliff file (pulzarraider) + * bug #11839 [FrameworkBundle] check if the Validator component is present when forms are enabled (xabbuh) + * bug #11418 [JsonResponse] Silent only JSON errors (GromNaN) + * bug #11937 [HttpKernel] Make sure HttpCache is a trusted proxy (thewilkybarkid) + * bug #11970 [Finder] Escape location for regex searches (ymc-dabe) + * bug #11837 Use getPathname() instead of string casting to get BinaryFileReponse file path (nervo) + * bug #11513 [Translation] made XliffFileDumper support CDATA sections. (hhamon) + * bug #11928 [Validator] The ratio of the ImageValidator is rounded to two decimals now (webmozart) + * bug #11907 [Intl] Improved bundle reader implementations (webmozart) + * bug #11874 [Console] guarded against non-traversable aliases (thierrymarianne) + * bug #11799 [YAML] fix handling of empty sequence items (xabbuh) + * bug #11906 [Intl] Fixed a few bugs in TextBundleWriter (webmozart) + * bug #11459 [Form][Validator] All index items after children are to be considered grand-children when resolving ViolationPath (Andrew Moore) + * bug #11715 [Form] FormBuilder::getIterator() now deals with resolved children (issei-m) + * bug #11892 [SwiftmailerBridge] Bump allowed versions of swiftmailer (ymc-dabe) + * bug #11918 [DependencyInjection] remove `service` parameter type from XSD (xabbuh) + * bug #11905 [Intl] Removed non-working $fallback argument from ArrayAccessibleResourceBundle (webmozart) + * bug #11497 Use separated function to resolve command and related arguments (JJK801) + * bug #11374 [DI] Added safeguards against invalid config in the YamlFileLoader (stof) + * bug #11897 [FrameworkBundle] Remove invalid markup (flack) + * bug #11860 [Security] Fix usage of unexistent method in DoctrineAclCache. (mauchede) + * bug #11850 [YAML] properly mask escape sequences in quoted strings (xabbuh) + * bug #11856 [FrameworkBundle] backport more error information from 2.6 to 2.3 (xabbuh) + * bug #11843 [Yaml] improve error message when detecting unquoted asterisks (xabbuh) + +* 2.5.4 (2014-09-03) + + * security #11832 CVE-2014-6072 (fabpot) + * security #11831 CVE-2014-5245 (stof) + * security #11830 CVE-2014-4931 (aitboudad, Jérémy Derussé) + * security #11829 CVE-2014-6061 (damz, fabpot) + * security #11828 CVE-2014-5244 (nicolas-grekas, larowlan) + * bug #10197 [FrameworkBundle] PhpExtractor bugfix and improvements (mtibben) + * bug #11772 [Filesystem] Add FTP stream wrapper context option to enable overwrite (Damian Sromek) + * bug #11791 [Process] fix mustRun() in sigchild environments (xabbuh) + * bug #11788 [Yaml] fixed mapping keys containing a quoted # (hvt, fabpot) + * bug #11787 fixed DateComparator if file does not exist (avi123) + * bug #11160 [DoctrineBridge] Abstract Doctrine Subscribers with tags (merk) + * bug #11768 [ClassLoader] Add a __call() method to XcacheClassLoader (tstoeckler) + * bug #11739 [Validator] Pass strict argument into the strict email validator (brianfreytag) + * bug #11749 [TwigBundle] Remove hard dependency of RequestContext in AssetsExtension (pgodel) + * bug #11726 [Filesystem Component] mkdir race condition fix #11626 (kcassam) + * bug #11677 [YAML] resolve variables in inlined YAML (xabbuh) + * bug #11639 [DependencyInjection] Fixed factory service not within the ServiceReferenceGraph. (boekkooi) + * bug #11778 [Validator] Fixed wrong translations for Collection constraints (samicemalone) + * bug #11756 [DependencyInjection] fix @return anno created by PhpDumper (jakubkulhan) + * bug #11711 [DoctrineBridge] Fix empty parameter logging in the dbal logger (jakzal) + * bug #11692 [DomCrawler] check for the correct field type (xabbuh) + * bug #11672 [Routing] fix handling of nullable XML attributes (xabbuh) + * bug #11624 [DomCrawler] fix the axes handling in a bc way (xabbuh) + * bug #11676 [Form] Fixed #11675 ValueToDuplicatesTransformer accept "0" value (Nek-) + * bug #11695 [Validators] Fixed failing tests requiring ICU 52.1 which are skipped otherwise (webmozart) + * bug #11584 [FrameworkBundle] Fixed validator factory definition when the Validator API is "auto" for PHP < 5.3.9 (webmozart) + * bug #11645 [Form] Fixed ValidatorExtension to work with the 2.5 Validation API (webmozart) + * bug #11529 [WebProfilerBundle] Fixed double height of canvas (hason) + * bug #11666 [DIC] Fixed: anonymous services are always private (lyrixx) + * bug #11641 [WebProfilerBundle ] Fix toolbar vertical alignment (blaugueux) + * bug #11637 fix dependencies on HttpFoundation component (xabbuh) + * bug #11559 [Validator] Convert objects to string in comparison validators (webmozart) + * feature #11510 [HttpFoundation] MongoDbSessionHandler supports auto expiry via configurable expiry_field (catchamonkey) + * bug #11408 [HttpFoundation] Update QUERY_STRING when overrideGlobals (yguedidi) + * bug #11625 [FrameworkBundle] resolve parameters before the configs are processed in the config:debug command (xabbuh) + * bug #11633 [FrameworkBundle] add missing attribute to XSD (xabbuh) + * bug #11601 [Validator] Allow basic auth in url when using UrlValidator. (blaugueux) + * bug #11609 [Console] fixed style creation when providing an unknown tag option (fabpot) + * bug #10914 [HttpKernel] added an analyze of environment parameters for built-in server (mauchede) + * bug #11598 [Finder] Shell escape and windows support (Gordon Franke, gimler) + * bug #11582 [DoctrineBridge] Changed UniqueEntityValidator to use the 2.5 Validation API (webmozart) + +* 2.5.3 (2014-08-06) + + * bug #11571 [Form] Fixed FormValidator compatibility with the non-BC 2.5 Validation API (webmozart) + * bug #11499 [BrowserKit] Fixed relative redirects for ambiguous paths (pkruithof) + * bug #11516 [BrowserKit] Fix browser kit redirect with ports (dakota) + * bug #11545 [Bundle][FrameworkBundle] built-in server: exit when docroot does not exist (xabbuh) + * bug #11560 Plural fix (1emming) + * bug #11558 [DependencyInjection] Fixed missing 'factory-class' attribute in XmlDumper output (kerdany) + * bug #11498 [Validator] Made it possible (again) to pass a class name to validatePropertyValue() (webmozart) + * bug #11548 [Component][DomCrawler] fix axes handling in Crawler::filterXPath() (xabbuh) + * bug #11422 [DependencyInjection] Self-referenced 'service_container' service breaks garbage collection (sun) + * bug #11428 [Serializer] properly handle null data when denormalizing (xabbuh) + * bug #10687 [Validator] Fixed string conversion in constraint violations (eagleoneraptor, webmozart) + * bug #11412 [Validator] Made sure that context changes don't leak out of (Contextual)ValidatorInterface (webmozart) + * bug #11475 [EventDispatcher] don't count empty listeners (xabbuh) + * bug #11436 fix signal handling in wait() on calls to stop() (xabbuh, romainneutron) + * bug #11469 [BrowserKit] Fixed server HTTP_HOST port uri conversion (bcremer, fabpot) + * bug #11425 Fix issue described in #11421 (Ben, ben-rosio) + * bug #11423 Pass a Scope instance instead of a scope name when cloning a container in the GrahpvizDumper (jakzal) + * bug #11448 [MonologBridge] fixed Console handler priorities (fabpot) + * bug #11454 [Validator] Fixed memory leak in ValidatorBuilder (webmozart) + * bug #11120 [Process] Reduce I/O load on Windows platform (romainneutron) + * bug #11370 [FrameworkBundle] avoid raising unexpected RuntimeException when specifying $_SERVER['KERNEL_DIR'] (iteman) + * bug #11342 [Form] Check if IntlDateFormatter constructor returned a valid object before using it (romainneutron) + * bug #11439 [ExpressionLanguage] Fixed double quoted string literals containing sharps (pylebecq) + * bug #11410 [Validator] Fixed object initializers in 2.5 version of the Validator (webmozart) + * bug #11411 [Validator] Backported #11410 to 2.3: Object initializers are called only once per object (webmozart) + * bug #11403 [Translator][FrameworkBundle] Added @ to the list of allowed chars in Translator (takeit) + * bug #11381 [Process] Use correct test for empty string in UnixPipes (whs, romainneutron) + +* 2.5.2 (2014-07-15) + + * [Security] Forced validate of locales passed to the translator + * bug #11350 [Form] solved dependency to ValidatorInterface, fix #11036 (Sebastian Blum) + * bug #11278 Remove Spaceless Blocks From Twig Templates (chrisguitarguy) + * feature #11367 [HttpFoundation] Fix to prevent magic bytes injection in JSONP responses... (CVE-2014-4671) (Andrew Moore) + * bug #11284 [Console] Remove estimated field from debug_nomax (bburnichon) + * bug #11386 Remove Spaceless Blocks from Twig Form Templates (chrisguitarguy) + * bug #9719 [TwigBundle] fix configuration tree for paths (mdavis1982, cordoval) + * bug #11244 [HttpFoundation] Remove body-related headers when sending the response, if body is empty (SimonSimCity) + +* 2.5.1 (2014-07-08) + + * bug #11283 [SecurityBundle] Remove Expression Language services when the component is unavailable (thewilkybarkid) + * bug #11238 [Translation] Added unescaping of ids in PoFileLoader (JustBlackBird) + * bug #11194 [DomCrawler] Remove the query string and the anchor of the uri of a link (benja-M-1) + * bug #11272 [Console] Make sure formatter is the same. (akimsko) + * bug #11259 [Config] Fixed failed config schema loads due to libxml_disable_entity_loader usage (ccorliss) + * bug #11234 [ClassLoader] fixed PHP warning on PHP 5.3 (fabpot) + * bug #11179 [Process] Fix ExecutableFinder with open basedir (cs278) + * bug #11242 [CssSelector] Refactored the CssSelector to remove the circular object graph (stof) + * bug #11219 [DomCrawler] properly handle buttons with single and double quotes insid... (xabbuh) + * bug #11220 [Components][Serializer] optional constructor arguments can be omitted during the denormalization process (xabbuh) + * bug #11186 Added missing `break` statement (apfelbox) + * bug #11168 [YAML] fix merge node (<<) (Tobion) + * bug #11170 [Console] Fixed notice in QuestionHelper (florianv) + * bug #11169 [Console] Fixed notice in DialogHelper (florianv) + * bug #11144 [HttpFoundation] Fixed Request::getPort returns incorrect value under IPv6 (kicken) + * bug #11121 [Process] Do not redirect output to file handles when output is disabled, simply discard it (romainneutron) + * bug #10966 PHP Fatal error when getContainer method of ContainerAwareCommand has be... (kevinvergauwen) + * bug #10981 [HttpFoundation] Fixed isSecure() check to be compliant with the docs (Jannik Zschiesche) + * bug #11117 [Validator] Fix array notation in the PropertyPath::append() (jakzal) + * bug #11113 [HttpKernel] Fix event dispatcher dependency (hacfi) + * bug #11111 Fixed undefined ImageValidator::$suffices property when uploading an image during functional tests (OwlyCode) + * bug #11099 [Debug] work-around https://bugs.php.net/61272 (nicolas-grekas) + * bug #11092 [HttpFoundation] Fix basic authentication in url with PHP-FPM (Kdecherf) + * bug #11097 [Debug] simplify code path to remove potential blank pages (nicolas-grekas) + * bug #10808 [DomCrawler] Empty select with attribute name="foo[]" bug fix (darles) + * bug #11063 [HttpFoundation] fix switch statement (Tobion) + * bug #11054 [Serializer] Xml encoder whitespace fix (fieg) + * bug #11009 [HttpFoundation] smaller fixes for PdoSessionHandler (Tobion) + * bug #11047 #10862 loadClassMetadata vs loadValidatorMetadata: revert default config (phramz) + * bug #11042 [Debug] fix debug handlers config (nicolas-grekas) + * bug #11043 [Console] OutputFormatter Unset Bold has wrong id (DZunke) + * bug #11033 [Debug] fix wrong case mismatch exception (nicolas-grekas) + * bug #11044 [Serializer] Fix BC break since 2.5 (fieg) + * bug #11041 Remove undefined variable $e (skydiablo) + +* 2.5.0 (2014-05-31) + + * bug #11014 [Validator] Remove property and method targets from the optional and required constraints (jakzal) + * bug #10983 [DomCrawler] Fixed charset detection in html5 meta charset tag (77web) + +* 2.5.0-RC1 (2014-05-28) + + * bug #10979 Make rootPath part of regex greedy (artursvonda) + * bug #10995 [TwigBridge][Trans]set %count% only on transChoice from the current context. (aitboudad) + * bug #10989 [Debug] throw even in stacking mode to preserve code paths (nicolas-grekas) + * bug #10987 [DomCrawler] Fixed a forgotten case of complex XPath queries (stof) + * feature #10930 [Process] Deprecate using values that are not string for Process::setStdin and ProcessBuilder::setInput (romainneutron) + * bug #10971 [Process] Fix conflicts between latest 2.3 fix and 2.5 deprecation (romainneutron) + * feature #10932 [Process] Deprecate Process::setStdin in favor of Process::setInput (romainneutron) + * bug #10849 [WIP][Finder] Fix wrong implementation on sortable callback comparator (ProPheT777) + * bug #10929 [Process] Add validation on Process input (romainneutron) + * bug #10946 [PropertyAccess] Fixed getValue() when accessing non-existing indices of ArrayAccess implementations (webmozart) + * bug #10958 [DomCrawler] Fixed filterXPath() chaining loosing the parent DOM nodes (stof, robbertkl) + * bug #10953 [HttpKernel] fixed file uploads in functional tests without file selected (realmfoo) + * feature #10941 [Debug] cleanup interfaces before 2.5-final (nicolas-grekas) + * bug #10947 [PropertyAccess] Fixed getValue() when accessing non-existing indices of ArrayAccess implementations (webmozart) + * bug #10937 [HttpKernel] Fix "absolute path" when we look to the cache directory (BenoitLeveque) + * bug #10933 Changed the default value of checkbox and radio to match the HTML spec (stof) + * bug #10927 [DomCrawler] Changed typehints form DomNode to DomElement (stof) + * bug #10894 [HttpKernel] removed absolute paths from the generated container (fabpot) + * bug #10926 [DomCrawler] Fixed the initial state for options without value attribute (stof) + * bug #10925 [DomCrawler] Fixed the handling of boolean attributes in ChoiceFormField (stof) + * feature #10882 Fix issue #10867 (umpirsky) + * bug #10902 [Yaml] Fixed YAML Parser does not ignore duplicate keys, violating YAML spec. (sun) + * feature #10912 [Form] Added support for injecting HttpFoundation's Request in ServerParams for the Validator extension (csarrazi) + * bug #10777 [Form] Automatically add step attribute to HTML5 time widgets to display seconds if needed (tucksaun) + * bug #10909 [PropertyAccess] Fixed plurals for -ves words (csarrazi) + * bug #10904 [HttpKernel] Replace sha1 with sha256 in recently added tests (jakzal) + * bug #10899 Explicitly define the encoding. (jakzal) + * bug #10897 [Console] Fix a console test (jakzal) + * bug #10896 [HttpKernel] Fixed cache behavior when TTL has expired and a default "global" TTL is defined (alquerci, fabpot) + * bug #10841 [DomCrawler] Fixed image input case sensitive (geoffrey-brier) + * bug #10714 [Console]Improve formatter for double-width character (denkiryokuhatsuden) + * bug #10872 [Form] Fixed TrimListenerTest as of PHP 5.5 (webmozart) + * feature #10880 [DependencyInjection] GraphvizDumper now displays unresolved parameters (rosstuck) + * bug #10876 [Console] Make `Helper\Table::setStyle()` chainable again (stloyd) + * bug #10762 [BrowserKit] Allow URLs that don't contain a path when creating a cookie from a string (thewilkybarkid) + * bug #10861 [Debug] enhance perf of DebugClassLoader (nicolas-grekas) + * bug #10863 [Security] Add check for supported attributes in AclVoter (artursvonda) + * bug #10854 [Debug] fix handling deprecated warnings and stacked errors turned into exceptions (nicolas-grekas) + * feature #10843 [TwigBridge] Added compile-time issues checking in twig:lint command (maxromanovsky) + * feature #10829 Fix issue 9172 (umpirsky) + * bug #10833 [TwigBridge][Transchoice] set %count% from the current context. (aitboudad) + * bug #10820 [WebProfilerBundle] Fixed profiler seach/homepage with empty token (tucksaun) + * bug #10809 Fixed composer to include config component for mocks in phpunit (jpauli) + * bug #10815 Fixed issue #5427 (umpirsky) + * bug #10817 [Debug] fix #10313: FlattenException not found (nicolas-grekas) + +* 2.5.0-BETA2 (2014-04-29) + + * bug #10803 [Debug] fix ErrorHandlerTest when context is not an array (nicolas-grekas) + * bug #10801 [Debug] ErrorHandler: remove $GLOBALS from context in PHP5.3 fix #10292 (nicolas-grekas) + * bug #10799 [Debug] less intrusive work around for https://bugs.php.net/54275 (nicolas-grekas) + * bug #10797 [HttpFoundation] Allow File instance to be passed to BinaryFileResponse (anlutro) + * bug #10798 [Console] Fix #10795: Allow instancing Console Application when STDIN is not declared (romainneutron) + * bug #10643 [TwigBridge] Removed strict check when found variables inside a translation (goetas) + * bug #10605 [ExpressionLanguage] Strict in_array check in Parser.php (parnas) + * bug #10789 [Console] Fixed the rendering of exceptions on HHVM with a terminal width (stof) + * bug #10773 [WebProfilerBundle ] Fixed an edge case on WDT loading (tucksaun) + * feature #10786 [FrameworkBundle] removed support for HHVM built-in web server as it is deprecated now (fabpot) + * bug #10784 [Security] removed $csrfTokenManager type hint from SimpleFormAuthenticationListener constructor argument (choonge) + * bug #10776 [Debug] fix #10771 DebugClassLoader can't load PSR4 libs (nicolas-grekas) + * bug #10763 [Process] Disable TTY mode on Windows platform (romainneutron) + * bug #10772 [Finder] Fix ignoring of unreadable dirs in the RecursiveDirectoryIterator (jakzal) + * bug #10757 [Process] Setting STDIN while running should not be possible (romainneutron) + * bug #10749 Fixed incompatibility of x509 auth with nginx (alcaeus) + * feature #10725 [Debug] Handled errors (nicolas-grekas) + * bug #10735 [Translation] [PluralizationRules] Little correction for case 'ar' (klyk50) + * bug #10720 [HttpFoundation] Fix DbalSessionHandler (Tobion) + * bug #10721 [HttpFoundation] status 201 is allowed to have a body (Tobion) + * bug #10728 [Process] Fix #10681, process are failing on Windows Server 2003 (romainneutron) + * bug #10733 [DomCrawler] Textarea value should default to empty string instead of null. (Berdir) + * bug #10723 [Security] fix DBAL connection typehint (Tobion) + * bug #10715 [Debug] Fixed ClassNotFoundFatalErrorHandler on windows. (lyrixx) + * bug #10700 Fixes various inconsistencies in the code (fabpot) + * bug #10697 [Translation] Make IcuDatFileLoader/IcuResFileLoader::load invalid resource compatible with HHVM. (idn2104) + +* 2.5.0-BETA1 (2014-04-11) + + * first beta release + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79cc6756b9b04..4479b927cf99e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,20 @@ Contributing ------------ -Symfony is an open source, community-driven project. If you'd like to contribute, -please read the [Contributing Code][1] part of the documentation. If you're submitting -a pull request, please follow the guidelines in the [Submitting a Patch][2] section -and use the [Pull Request Template][3]. +Symfony2 is an open source, community-driven project. + +If you'd like to contribute, please read the following documents: + +* [Contributing Code][1]: The document index related to contributions; + +* [Submitting a Patch][2]: Guidelines for submitting a pull request; + +* [Pull Request Template][3]: Template header to use in your pull request + description; + +* [Backwards Compatibility][4]: Backward compatibility rules. [1]: http://symfony.com/doc/current/contributing/code/index.html [2]: http://symfony.com/doc/current/contributing/code/patches.html#check-list [3]: http://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request +[4]: http://symfony.com/doc/current/contributing/code/bc.html#working-on-symfony-code diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md index f203b208fd951..ff3dc0a1860e0 100644 --- a/UPGRADE-2.2.md +++ b/UPGRADE-2.2.md @@ -23,7 +23,7 @@ #### Deprecations - * The `standalone` option is deprecated and will replaced with the `strategy` option in 2.3. + * The `standalone` option is deprecated and will be replaced with the `strategy` option in 2.3. * The values `true`, `false`, `js` for the `standalone` option were deprecated and replaced respectively with the `esi`, `inline`, `hinclude` in 2.3. diff --git a/UPGRADE-2.4.md b/UPGRADE-2.4.md new file mode 100644 index 0000000000000..bd9ecc3c4dae4 --- /dev/null +++ b/UPGRADE-2.4.md @@ -0,0 +1,9 @@ +UPGRADE FROM 2.3 to 2.4 +======================= + +Form +---- + + * The constructor parameter `$precision` in `IntegerToLocalizedStringTransformer` + is now ignored completely, because a precision does not make sense for + integers. diff --git a/UPGRADE-2.5.md b/UPGRADE-2.5.md new file mode 100644 index 0000000000000..14323cc88307a --- /dev/null +++ b/UPGRADE-2.5.md @@ -0,0 +1,263 @@ +UPGRADE FROM 2.4 to 2.5 +======================= + +Routing +------- + + * Added a new optional parameter `$requiredSchemes` to `Symfony\Component\Routing\Generator\UrlGenerator::doGenerate()` + +Form +---- + + * The method `FormInterface::getErrors()` now returns an instance of + `Symfony\Component\Form\FormErrorIterator` instead of an array. This object + is traversable, countable and supports array access. However, you can not + pass it to any of PHP's `array_*` functions anymore. You should use + `iterator_to_array()` in those cases where you did. + + Before: + + ``` + $errors = array_map($callback, $form->getErrors()); + ``` + + After: + + ``` + $errors = array_map($callback, iterator_to_array($form->getErrors())); + ``` + + * The method `FormInterface::getErrors()` now has two additional, optional + parameters. Make sure to add these parameters to the method signatures of + your implementations of that interface. + + Before: + + ``` + public function getErrors() + { + ``` + + After: + + ``` + public function getErrors($deep = false, $flatten = true) + { + ``` + + Before: + + ``` + {% if form.vars.errors %} + ``` + + After: + + ``` + {% if form.vars.errors|length %} + ``` + +PropertyAccess +-------------- + + * The methods `isReadable()` and `isWritable()` were added to + `PropertyAccessorInterface`. If you implemented this interface in your own + code, you should add these two methods. + + * The methods `getValue()` and `setValue()` now throw an + `NoSuchIndexException` instead of a `NoSuchPropertyException` when an index + is accessed on an object that does not implement `ArrayAccess`. If you catch + this exception in your code, you should adapt the catch statement: + + Before: + + ```php + $object = new \stdClass(); + + try { + $propertyAccessor->getValue($object, '[index]'); + $propertyAccessor->setValue($object, '[index]', 'New value'); + } catch (NoSuchPropertyException $e) { + // ... + } + ``` + + After: + + ```php + $object = new \stdClass(); + + try { + $propertyAccessor->getValue($object, '[index]'); + $propertyAccessor->setValue($object, '[index]', 'New value'); + } catch (NoSuchIndexException $e) { + // ... + } + ``` + + A `NoSuchPropertyException` is still thrown when a non-existing property is + accessed on an object or an array. + +Validator +--------- + + * EmailValidator has changed to allow `non-strict` and `strict` email validation + + Before: + + Email validation was done with php's `filter_var()` + + After: + + Default email validation is now done via a simple regex which may cause invalid emails (not RFC compilant) to be + valid. This is the default behaviour. + + Strict email validation has to be explicitly activated in the configuration file by adding + + ``` + framework: + //... + validation: + strict_email: true + //... + + ``` + + Also you have to add to your composer.json: + + ``` + "egulias/email-validator": "~1.2" + ``` + + * `ClassMetadata::getGroupSequence()` now returns `GroupSequence` instances + instead of an array. The sequence implements `\Traversable`, `\ArrayAccess` + and `\Countable`, so in most cases you should be fine. If you however use the + sequence with PHP's `array_*()` functions, you should cast it to an array + first using `iterator_to_array()`: + + Before: + + ``` + $sequence = $metadata->getGroupSequence(); + $result = array_map($callback, $sequence); + ``` + + After: + + ``` + $sequence = iterator_to_array($metadata->getGroupSequence()); + $result = array_map($callback, $sequence); + ``` + + * The array type hint in `ClassMetadata::setGroupSequence()` was removed. If + you overwrite this method, make sure to remove the type hint as well. The + method should now accept `GroupSequence` instances just as well as arrays. + + Before: + + ``` + public function setGroupSequence(array $groups) + { + // ... + } + ``` + + After: + + ``` + public function setGroupSequence($groupSequence) + { + // ... + } + ``` + + * The validation engine in `Symfony\Component\Validator\Validator` was replaced + by a new one in `Symfony\Component\Validator\Validator\RecursiveValidator`. + With that change, several classes were deprecated that will be removed in + Symfony 3.0. Also, the API of the validator was slightly changed. More + details about that can be found in UPGRADE-3.0. + + You can choose the desired API via the new "api" entry in + app/config/config.yml: + + ``` + framework: + validation: + enabled: true + api: auto + ``` + + When running PHP 5.3.9 or higher, Symfony will then use an implementation + that supports both the old API and the new one: + + ``` + framework: + validation: + enabled: true + api: 2.5-bc + ``` + + When running PHP lower than 5.3.9, that compatibility layer is not supported. + On those versions, the old implementation will be used instead: + + ``` + framework: + validation: + enabled: true + api: 2.4 + ``` + + If you develop a new application that doesn't rely on the old API, you can + also set the API to 2.5. In that case, the backwards compatibility layer + will not be activated: + + ``` + framework: + validation: + enabled: true + api: 2.5 + ``` + + When using the validator outside of the Symfony full-stack framework, the + desired API can be selected using `setApiVersion()` on the validator builder: + + ``` + // Previous implementation + $validator = Validation::createValidatorBuilder() + ->setApiVersion(Validation::API_VERSION_2_4) + ->getValidator(); + + // New implementation with backwards compatibility support + $validator = Validation::createValidatorBuilder() + ->setApiVersion(Validation::API_VERSION_2_5_BC) + ->getValidator(); + + // New implementation without backwards compatibility support + $validator = Validation::createValidatorBuilder() + ->setApiVersion(Validation::API_VERSION_2_5) + ->getValidator(); + ``` + + +Yaml Component +-------------- + + * The way Yaml handles duplicate keys in an array was changed from `rewrite with the + last element` behavior to ignoring all the elements with the same key after the first one. + + Example: + + ``` + parentElement: + firstChild: foo + secondChild: 123 + firstChild: bar + ``` + + Before: + + This would be parsed in an array like this: `["parentElement" => ["firstChild" => "bar", "secondChild" => 123]]` + + After: + + The first value is used: `["parentElement" => ["firstChild" => "foo", "secondChild" => 123]]` diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index f8dcd0b5e460c..14520c1c6fa7a 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -254,7 +254,7 @@ UPGRADE FROM 2.x to 3.0 and all of its implementations were removed. Use the new interface `Symfony\Component\Security\Csrf\CsrfTokenManagerInterface` instead. - * The options "`csrf_provider`" and "`intention`" were renamed to "`csrf_token_generator`" + * The options "`csrf_provider`" and "`intention`" were renamed to "`csrf_token_generator`" and "`csrf_token_id`". * The method `Form::getErrorsAsString()` was removed. Use `Form::getErrors()` @@ -273,6 +273,10 @@ UPGRADE FROM 2.x to 3.0 echo $form->getErrors(true, false); ``` + ```php + echo $form->getErrors(true, false); + ``` + ### FrameworkBundle * The `getRequest` method of the base `Controller` class has been deprecated diff --git a/composer.json b/composer.json index 4cb0f424496e6..1d893fef234d7 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "symfony/doctrine-bridge": "self.version", "symfony/dom-crawler": "self.version", "symfony/event-dispatcher": "self.version", + "symfony/expression-language": "self.version", "symfony/filesystem": "self.version", "symfony/finder": "self.version", "symfony/form": "self.version", @@ -48,6 +49,10 @@ "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-http": "self.version", "symfony/security-bundle": "self.version", "symfony/serializer": "self.version", "symfony/stopwatch": "self.version", @@ -67,7 +72,8 @@ "monolog/monolog": "~1.3", "propel/propel1": "~1.6", "ircmaxell/password-compat": "~1.0", - "ocramius/proxy-manager": "~0.3.1" + "ocramius/proxy-manager": "~0.4|~1.0", + "egulias/email-validator": "~1.2" }, "autoload": { "psr-0": { "Symfony\\": "src/" }, @@ -80,7 +86,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ce0621d0a9eee..8af153f61ef26 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,7 @@ ./src/Symfony/Bridge/*/Tests/ ./src/Symfony/Component/*/Tests/ + ./src/Symfony/Component/*/*/Tests/ ./src/Symfony/Bundle/*/Tests/ @@ -33,6 +34,7 @@ ./src/Symfony/Bridge/*/Tests ./src/Symfony/Component/*/Tests + ./src/Symfony/Component/*/*/Tests ./src/Symfony/Bundle/*/Tests ./src/Symfony/Bundle/*/Resources ./src/Symfony/Component/*/Resources diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 745a0a87d4fe6..35fadc2cad07a 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -304,14 +304,30 @@ protected function detectMetadataDriver($dir, ContainerBuilder $container) */ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName) { - $cacheDriver = $objectManager[$cacheName.'_driver']; - $cacheDriverService = $this->getObjectManagerElementName($objectManager['name'].'_'.$cacheName); + $this->loadCacheDriver($cacheName, $objectManager['name'], $objectManager[$cacheName.'_driver'], $container); + } + + /** + * Loads a cache driver. + * + * @param string $cacheDriverServiceId The cache driver name. + * @param string $objectManagerName The object manager name. + * @param array $cacheDriver The cache driver mapping. + * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container The ContainerBuilder instance. + * + * @return string + * + * @throws \InvalidArgumentException + */ + protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container) + { + $cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName.'_'.$cacheName); switch ($cacheDriver['type']) { case 'service': - $container->setAlias($cacheDriverService, new Alias($cacheDriver['id'], false)); + $container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false)); - return; + return $cacheDriverServiceId; case 'memcache': $memcacheClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcache.class').'%'; $memcacheInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcache_instance.class').'%'; @@ -322,8 +338,8 @@ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerB $memcacheInstance->addMethodCall('connect', array( $memcacheHost, $memcachePort, )); - $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcache_instance', $objectManager['name'])), $memcacheInstance); - $cacheDef->addMethodCall('setMemcache', array(new Reference($this->getObjectManagerElementName(sprintf('%s_memcache_instance', $objectManager['name']))))); + $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcache_instance', $objectManagerName)), $memcacheInstance); + $cacheDef->addMethodCall('setMemcache', array(new Reference($this->getObjectManagerElementName(sprintf('%s_memcache_instance', $objectManagerName))))); break; case 'memcached': $memcachedClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcached.class').'%'; @@ -335,8 +351,8 @@ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerB $memcachedInstance->addMethodCall('addServer', array( $memcachedHost, $memcachedPort, )); - $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManager['name'])), $memcachedInstance); - $cacheDef->addMethodCall('setMemcached', array(new Reference($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManager['name']))))); + $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)), $memcachedInstance); + $cacheDef->addMethodCall('setMemcached', array(new Reference($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName))))); break; case 'redis': $redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%'; @@ -348,8 +364,8 @@ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerB $redisInstance->addMethodCall('connect', array( $redisHost, $redisPort, )); - $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManager['name'])), $redisInstance); - $cacheDef->addMethodCall('setRedis', array(new Reference($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManager['name']))))); + $container->setDefinition($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)), $redisInstance); + $cacheDef->addMethodCall('setRedis', array(new Reference($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName))))); break; case 'apc': case 'array': @@ -363,11 +379,21 @@ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerB } $cacheDef->setPublic(false); - // generate a unique namespace for the given application - $namespace = 'sf2'.$this->getMappingResourceExtension().'_'.$objectManager['name'].'_'.md5($container->getParameter('kernel.root_dir').$container->getParameter('kernel.environment')); - $cacheDef->addMethodCall('setNamespace', array($namespace)); - $container->setDefinition($cacheDriverService, $cacheDef); + if (!isset($cacheDriver['namespace'])) { + // generate a unique namespace for the given application + $env = $container->getParameter('kernel.root_dir').$container->getParameter('kernel.environment'); + $hash = hash('sha256', $env); + $namespace = 'sf2'.$this->getMappingResourceExtension().'_'.$objectManagerName.'_'.$hash; + + $cacheDriver['namespace'] = $namespace; + } + + $cacheDef->addMethodCall('setNamespace', array($cacheDriver['namespace'])); + + $container->setDefinition($cacheDriverServiceId, $cacheDef); + + return $cacheDriverServiceId; } /** diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 57f3d9dece139..94f72fd8c8c05 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -53,6 +53,13 @@ public function process(ContainerBuilder $container) return; } + $taggedSubscribers = $container->findTaggedServiceIds($this->tagPrefix.'.event_subscriber'); + $taggedListeners = $container->findTaggedServiceIds($this->tagPrefix.'.event_listener'); + + if (empty($taggedSubscribers) && empty($taggedListeners)) { + return; + } + $this->container = $container; $this->connections = $container->getParameter($this->connections); $sortFunc = function ($a, $b) { @@ -62,34 +69,38 @@ public function process(ContainerBuilder $container) return $a > $b ? -1 : 1; }; - $subscribersPerCon = $this->groupByConnection($container->findTaggedServiceIds($this->tagPrefix.'.event_subscriber')); - foreach ($subscribersPerCon as $con => $subscribers) { - $em = $this->getEventManager($con); + if (!empty($taggedSubscribers)) { + $subscribersPerCon = $this->groupByConnection($taggedSubscribers); + foreach ($subscribersPerCon as $con => $subscribers) { + $em = $this->getEventManager($con); - uasort($subscribers, $sortFunc); - foreach ($subscribers as $id => $instance) { - if ($container->getDefinition($id)->isAbstract()) { - throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event subscriber.', $id)); - } + uasort($subscribers, $sortFunc); + foreach ($subscribers as $id => $instance) { + if ($container->getDefinition($id)->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event subscriber.', $id)); + } - $em->addMethodCall('addEventSubscriber', array(new Reference($id))); + $em->addMethodCall('addEventSubscriber', array(new Reference($id))); + } } } - $listenersPerCon = $this->groupByConnection($container->findTaggedServiceIds($this->tagPrefix.'.event_listener'), true); - foreach ($listenersPerCon as $con => $listeners) { - $em = $this->getEventManager($con); + if (!empty($taggedListeners)) { + $listenersPerCon = $this->groupByConnection($taggedListeners, true); + foreach ($listenersPerCon as $con => $listeners) { + $em = $this->getEventManager($con); - uasort($listeners, $sortFunc); - foreach ($listeners as $id => $instance) { - if ($container->getDefinition($id)->isAbstract()) { - throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event listener.', $id)); - } + uasort($listeners, $sortFunc); + foreach ($listeners as $id => $instance) { + if ($container->getDefinition($id)->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event listener.', $id)); + } - $em->addMethodCall('addEventListener', array( - array_unique($instance['event']), - isset($instance['lazy']) && $instance['lazy'] ? $id : new Reference($id), - )); + $em->addMethodCall('addEventListener', array( + array_unique($instance['event']), + isset($instance['lazy']) && $instance['lazy'] ? $id : new Reference($id), + )); + } } } } diff --git a/src/Symfony/Bridge/Doctrine/ExpressionLanguage/DoctrineParserCache.php b/src/Symfony/Bridge/Doctrine/ExpressionLanguage/DoctrineParserCache.php new file mode 100644 index 0000000000000..1c0e8cdfee26e --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/ExpressionLanguage/DoctrineParserCache.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\Doctrine\ExpressionLanguage; + +use Doctrine\Common\Cache\Cache; +use Symfony\Component\ExpressionLanguage\ParsedExpression; +use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface; + +/** + * @author Adrien Brault + */ +class DoctrineParserCache implements ParserCacheInterface +{ + /** + * @var Cache + */ + private $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function fetch($key) + { + if (false === $value = $this->cache->fetch($key)) { + return; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function save($key, ParsedExpression $expression) + { + $this->cache->save($key, $expression); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php index 389ce686115ff..3b5c6d5df8feb 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php @@ -294,6 +294,8 @@ public function getValuesForChoices(array $entities) * @return array * * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface + * + * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForChoices(array $entities) { @@ -334,6 +336,8 @@ public function getIndicesForChoices(array $entities) * @return array * * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface + * + * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForValues(array $values) { diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index ccd8a25f165ef..b18cc027762e9 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -25,12 +25,11 @@ class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface { protected $registry; - private $cache; + private $cache = array(); public function __construct(ManagerRegistry $registry) { $this->registry = $registry; - $this->cache = array(); } /** @@ -91,7 +90,7 @@ public function guessRequired($class, $property) return; } - /* @var ClassMetadataInfo $classMetadata */ + /** @var ClassMetadataInfo $classMetadata */ $classMetadata = $classMetadatas[0]; // Check whether the field exists and is nullable or not diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index aac695c0b3755..ba4213aeb639e 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -114,7 +114,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) ? spl_object_hash($options['group_by']) : $options['group_by']; - $hash = md5(json_encode(array( + $hash = hash('sha256', json_encode(array( spl_object_hash($options['em']), $options['class'], $propertyHash, diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index d62b54478b4ac..3a4feece1cee9 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -46,7 +46,7 @@ class DoctrineTokenProvider implements TokenProviderInterface private $conn; /** - * new DoctrineTokenProvider for the RemembeMe authentication service + * new DoctrineTokenProvider for the RememberMe authentication service * * @param \Doctrine\DBAL\Connection $conn */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php index b5985caed47f9..5185a75998d66 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php @@ -18,10 +18,6 @@ class ContainerAwareEventManagerTest extends \PHPUnit_Framework_TestCase { protected function setUp() { - if (!class_exists('Symfony\Component\DependencyInjection\Container')) { - $this->markTestSkipped('The "DependencyInjection" component is not available'); - } - $this->container = new Container(); $this->evm = new ContainerAwareEventManager($this->container); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php index 175cc560b756e..bad478c568bbe 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php @@ -18,17 +18,6 @@ class DoctrineDataCollectorTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Doctrine\DBAL\Platforms\MySqlPlatform')) { - $this->markTestSkipped('Doctrine DBAL is not available.'); - } - - if (!class_exists('Symfony\Component\HttpKernel\HttpKernel')) { - $this->markTestSkipped('The "HttpKernel" component is not available'); - } - } - public function testCollectConnections() { $c = $this->createCollector(array()); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php index 914c8938dbbae..53ad5a0e3a8a7 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php @@ -16,17 +16,6 @@ class ContainerAwareLoaderTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Symfony\Component\DependencyInjection\Container')) { - $this->markTestSkipped('The "DependencyInjection" component is not available'); - } - - if (!class_exists('Doctrine\Common\DataFixtures\Loader')) { - $this->markTestSkipped('Doctrine Data Fixtures is not available.'); - } - } - public function testShouldSetContainerOnContainerAwareFixture() { $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index ae8a76750f19b..b93cde7962729 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -17,13 +17,6 @@ class RegisterEventListenersAndSubscribersPassTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Symfony\Component\DependencyInjection\Container')) { - $this->markTestSkipped('The "DependencyInjection" component is not available'); - } - } - /** * @expectedException InvalidArgumentException */ @@ -146,6 +139,17 @@ public function testProcessEventSubscribersWithPriorities() $this->assertEquals(array('b', 'a'), $serviceOrder); } + public function testProcessNoTaggedServices() + { + $container = $this->createBuilder(true); + + $this->process($container); + + $this->assertEquals(array(), $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls()); + + $this->assertEquals(array(), $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls()); + } + private function process(ContainerBuilder $container) { $pass = new RegisterEventListenersAndSubscribersPass('doctrine.connections', 'doctrine.dbal.%s_connection.event_manager', 'doctrine'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php new file mode 100644 index 0000000000000..19e550f3ef801 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -0,0 +1,162 @@ + +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +namespace Symfony\Bridge\Doctrine\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; + +/** + * @author Fabio B. Silva + */ +class DoctrineExtensionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension + */ + private $extension; + + protected function setUp() + { + parent::setUp(); + + $this->extension = $this + ->getMockBuilder('Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension') + ->setMethods(array( + 'getMappingResourceConfigDirectory', + 'getObjectManagerElementName', + 'getMappingObjectDefaultName', + 'getMappingResourceExtension', + 'load', + )) + ->getMock() + ; + + $this->extension->expects($this->any()) + ->method('getObjectManagerElementName') + ->will($this->returnCallback(function ($name) { + return 'doctrine.orm.'.$name; + })); + } + + public function providerBasicDrivers() + { + return array( + array('doctrine.orm.cache.apc.class', array('type' => 'apc')), + array('doctrine.orm.cache.array.class', array('type' => 'array')), + array('doctrine.orm.cache.xcache.class', array('type' => 'xcache')), + array('doctrine.orm.cache.wincache.class', array('type' => 'wincache')), + array('doctrine.orm.cache.zenddata.class', array('type' => 'zenddata')), + array('doctrine.orm.cache.redis.class', array('type' => 'redis'), array('setRedis')), + array('doctrine.orm.cache.memcache.class', array('type' => 'memcache'), array('setMemcache')), + array('doctrine.orm.cache.memcached.class', array('type' => 'memcached'), array('setMemcached')), + ); + } + + /** + * @param string $class + * @param array $config + * + * @dataProvider providerBasicDrivers + */ + public function testLoadBasicCacheDriver($class, array $config, array $expectedCalls = array()) + { + $container = $this->createContainer(); + $cacheName = 'metadata_cache'; + $objectManager = array( + 'name' => 'default', + 'metadata_cache_driver' => $config, + ); + + $this->invokeLoadCacheDriver($objectManager, $container, $cacheName); + + $this->assertTrue($container->hasDefinition('doctrine.orm.default_metadata_cache')); + + $definition = $container->getDefinition('doctrine.orm.default_metadata_cache'); + $defCalls = $definition->getMethodCalls(); + $expectedCalls[] = 'setNamespace'; + $actualCalls = array_map(function ($call) { + return $call[0]; + }, $defCalls); + + $this->assertFalse($definition->isPublic()); + $this->assertEquals("%$class%", $definition->getClass()); + + foreach (array_unique($expectedCalls) as $call) { + $this->assertContains($call, $actualCalls); + } + } + + public function testServiceCacheDriver() + { + $cacheName = 'metadata_cache'; + $container = $this->createContainer(); + $definition = new Definition('%doctrine.orm.cache.apc.class%'); + $objectManager = array( + 'name' => 'default', + 'metadata_cache_driver' => array( + 'type' => 'service', + 'id' => 'service_driver', + ), + ); + + $container->setDefinition('service_driver', $definition); + + $this->invokeLoadCacheDriver($objectManager, $container, $cacheName); + + $this->assertTrue($container->hasAlias('doctrine.orm.default_metadata_cache')); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage "unrecognized_type" is an unrecognized Doctrine cache driver. + */ + public function testUnrecognizedCacheDriverException() + { + $cacheName = 'metadata_cache'; + $container = $this->createContainer(); + $objectManager = array( + 'name' => 'default', + 'metadata_cache_driver' => array( + 'type' => 'unrecognized_type', + ), + ); + + $this->invokeLoadCacheDriver($objectManager, $container, $cacheName); + } + + protected function invokeLoadCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName) + { + $method = new \ReflectionMethod($this->extension, 'loadObjectManagerCacheDriver'); + + $method->setAccessible(true); + + $method->invokeArgs($this->extension, array($objectManager, $container, $cacheName)); + } + + /** + * @param array $data + * + * @return \Symfony\Component\DependencyInjection\ContainerBuilder + */ + protected function createContainer(array $data = array()) + { + return new ContainerBuilder(new ParameterBag(array_merge(array( + 'kernel.bundles' => array('FrameworkBundle' => 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle'), + 'kernel.cache_dir' => __DIR__, + 'kernel.debug' => false, + 'kernel.environment' => 'test', + 'kernel.name' => 'kernel', + 'kernel.root_dir' => __DIR__, + ), $data))); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php b/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php index 970c9973140ff..577d1985990a5 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php @@ -21,21 +21,6 @@ */ abstract class DoctrineOrmTestCase extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Doctrine\Common\Version')) { - $this->markTestSkipped('Doctrine Common is not available.'); - } - - if (!class_exists('Doctrine\DBAL\Platforms\MySqlPlatform')) { - $this->markTestSkipped('Doctrine DBAL is not available.'); - } - - if (!class_exists('Doctrine\ORM\EntityManager')) { - $this->markTestSkipped('Doctrine ORM is not available.'); - } - } - /** * @return \Doctrine\ORM\EntityManager */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/ExpressionLanguage/DoctrineParserCacheTest.php b/src/Symfony/Bridge/Doctrine/Tests/ExpressionLanguage/DoctrineParserCacheTest.php new file mode 100644 index 0000000000000..a473b3ace2fe5 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/ExpressionLanguage/DoctrineParserCacheTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\ExpressionLanguage; + +use Symfony\Bridge\Doctrine\ExpressionLanguage\DoctrineParserCache; + +class DoctrineParserCacheTest extends \PHPUnit_Framework_TestCase +{ + public function testFetch() + { + $doctrineCacheMock = $this->getMock('Doctrine\Common\Cache\Cache'); + $parserCache = new DoctrineParserCache($doctrineCacheMock); + + $doctrineCacheMock->expects($this->once()) + ->method('fetch') + ->will($this->returnValue('bar')); + + $result = $parserCache->fetch('foo'); + + $this->assertEquals('bar', $result); + } + + public function testFetchUnexisting() + { + $doctrineCacheMock = $this->getMock('Doctrine\Common\Cache\Cache'); + $parserCache = new DoctrineParserCache($doctrineCacheMock); + + $doctrineCacheMock + ->expects($this->once()) + ->method('fetch') + ->will($this->returnValue(false)); + + $this->assertNull($parserCache->fetch('')); + } + + public function testSave() + { + $doctrineCacheMock = $this->getMock('Doctrine\Common\Cache\Cache'); + $parserCache = new DoctrineParserCache($doctrineCacheMock); + + $expression = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParsedExpression') + ->disableOriginalConstructor() + ->getMock(); + + $doctrineCacheMock->expects($this->once()) + ->method('save') + ->with('foo', $expression); + + $parserCache->save('foo', $expression); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index e7b3cfc96bfb9..75f8fdc0dc20a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -50,22 +50,6 @@ protected function getExtensions() protected function setUp() { - if (!class_exists('Symfony\Component\Form\Form')) { - $this->markTestSkipped('The "Form" component is not available'); - } - - if (!class_exists('Doctrine\DBAL\Platforms\MySqlPlatform')) { - $this->markTestSkipped('Doctrine DBAL is not available.'); - } - - if (!class_exists('Doctrine\Common\Version')) { - $this->markTestSkipped('Doctrine Common is not available.'); - } - - if (!class_exists('Doctrine\ORM\EntityManager')) { - $this->markTestSkipped('Doctrine ORM is not available.'); - } - $this->em = DoctrineOrmTestCase::createTestEntityManager(); parent::setUp(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php b/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php index 8329476f1f538..4f82bc37bbb79 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php @@ -20,13 +20,6 @@ */ class DbalSessionHandlerTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Symfony\Component\HttpFoundation\Request')) { - $this->markTestSkipped('The "HttpFoundation" component is not available'); - } - } - public function testConstruct() { $connection = $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index f791f97f01ff0..8c179cd31f246 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -18,15 +18,6 @@ class EntityUserProviderTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Symfony\Component\Security\Core\SecurityContext')) { - $this->markTestSkipped('The "Security" component is not available'); - } - - parent::setUp(); - } - public function testRefreshUserGetsUserByPrimaryKey() { $em = DoctrineTestHelper::createTestEntityManager(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidator2Dot4ApiTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidator2Dot4ApiTest.php new file mode 100644 index 0000000000000..3e1151d520b46 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidator2Dot4ApiTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Validator\Constraints; + +use Symfony\Component\Validator\Validation; + +/** + * @since 2.5.4 + * @author Bernhard Schussek + */ +class LegacyUniqueEntityValidator2Dot4ApiTest extends UniqueEntityValidatorTest +{ + protected function getApiVersion() + { + return Validation::API_VERSION_2_4; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidatorLegacyApiTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidatorLegacyApiTest.php new file mode 100644 index 0000000000000..07382d90e9d67 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidatorLegacyApiTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Validator\Constraints; + +use Symfony\Component\Validator\Validation; + +/** + * @since 2.5.4 + * @author Bernhard Schussek + */ +class LegacyUniqueEntityValidatorLegacyApiTest extends UniqueEntityValidatorTest +{ + protected function getApiVersion() + { + return Validation::API_VERSION_2_5_BC; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 3cf2cbcc6b0e3..cfc56b9b4c88d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -16,12 +16,13 @@ use Doctrine\Common\Persistence\ObjectRepository; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; -use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator; +use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest; +use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Validator; use Doctrine\ORM\Tools\SchemaTool; @@ -47,6 +48,11 @@ class UniqueEntityValidatorTest extends AbstractConstraintValidatorTest */ protected $repository; + protected function getApiVersion() + { + return Validation::API_VERSION_2_5; + } + protected function setUp() { $this->em = DoctrineTestHelper::createTestEntityManager(); @@ -239,6 +245,38 @@ public function testValidateUniquenessWithIgnoreNull() ->assertRaised(); } + public function testValidateUniquenessAfterConsideringMultipleQueryResults() + { + $constraint = new UniqueEntity(array( + 'message' => 'myMessage', + 'fields' => array('name'), + 'em' => self::EM_NAME, + )); + + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Foo'); + + $this->em->persist($entity1); + $this->em->persist($entity2); + $this->em->flush(); + + $this->validator->validate($entity1, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setInvalidValue('Foo') + ->assertRaised(); + + $this->context->getViolations()->remove(0); + + $this->validator->validate($entity2, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setInvalidValue('Foo') + ->assertRaised(); + } + public function testValidateUniquenessUsingCustomRepositoryMethod() { $constraint = new UniqueEntity(array( diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index da66a4b9f6d41..48c35c9eff3a5 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -46,6 +46,10 @@ public function __construct(ManagerRegistry $registry) */ public function validate($entity, Constraint $constraint) { + if (!$constraint instanceof UniqueEntity) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UniqueEntity'); + } + if (!is_array($constraint->fields) && !is_string($constraint->fields)) { throw new UnexpectedTypeException($constraint->fields, 'array'); } @@ -132,6 +136,9 @@ public function validate($entity, Constraint $constraint) $errorPath = null !== $constraint->errorPath ? $constraint->errorPath : $fields[0]; - $this->context->addViolationAt($errorPath, $constraint->message, array(), $criteria[$fields[0]]); + $this->buildViolation($constraint->message) + ->atPath($errorPath) + ->setInvalidValue($criteria[$fields[0]]) + ->addViolation(); } } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 9ae21d4ab3381..0d5a32fb2b2a9 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -21,12 +21,13 @@ }, "require-dev": { "symfony/stopwatch": "~2.2", - "symfony/dependency-injection": "~2.0,>=2.0.5", + "symfony/dependency-injection": "~2.2", "symfony/form": "~2.3,>=2.3.8", "symfony/http-kernel": "~2.2", "symfony/property-access": "~2.3", "symfony/security": "~2.2", - "symfony/validator": "~2.3.0,>=2.3.20", + "symfony/expression-language": "~2.2", + "symfony/validator": "~2.5,>=2.5.5", "symfony/translation": "~2.0,>=2.0.5", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.2", @@ -46,7 +47,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bridge/Monolog/CHANGELOG.md b/src/Symfony/Bridge/Monolog/CHANGELOG.md index 88ecedd5f6b5c..73239fa86b649 100644 --- a/src/Symfony/Bridge/Monolog/CHANGELOG.md +++ b/src/Symfony/Bridge/Monolog/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +2.4.0 +----- + + * added ConsoleHandler and ConsoleFormatter which can be used to show log messages + in the console output depending on the verbosity settings + 2.1.0 ----- diff --git a/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php new file mode 100644 index 0000000000000..58bc426cb5e3c --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Formatter; + +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; + +/** + * Formats incoming records for console output by coloring them depending on log level. + * + * @author Tobias Schultze + */ +class ConsoleFormatter extends LineFormatter +{ + const SIMPLE_FORMAT = "%start_tag%[%datetime%] %channel%.%level_name%:%end_tag% %message% %context% %extra%\n"; + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + if ($record['level'] >= Logger::ERROR) { + $record['start_tag'] = ''; + $record['end_tag'] = ''; + } elseif ($record['level'] >= Logger::NOTICE) { + $record['start_tag'] = ''; + $record['end_tag'] = ''; + } elseif ($record['level'] >= Logger::INFO) { + $record['start_tag'] = ''; + $record['end_tag'] = ''; + } else { + $record['start_tag'] = ''; + $record['end_tag'] = ''; + } + + return parent::format($record); + } +} diff --git a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php index 873632e696ddb..dbfec46da3f02 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php @@ -14,7 +14,6 @@ use Monolog\Handler\ChromePHPHandler as BaseChromePhpHandler; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; /** * ChromePhpHandler. @@ -38,7 +37,7 @@ class ChromePhpHandler extends BaseChromePhpHandler */ public function onKernelResponse(FilterResponseEvent $event) { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + if (!$event->isMasterRequest()) { return; } diff --git a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php new file mode 100644 index 0000000000000..a4c19cdb6af1d --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Handler; + +use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Logger; +use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Writes logs to the console output depending on its verbosity setting. + * + * It is disabled by default and gets activated as soon as a command is executed. + * Instead of listening to the console events, the output can also be set manually. + * + * The minimum logging level at which this handler will be triggered depends on the + * verbosity setting of the console output. The default mapping is: + * - OutputInterface::VERBOSITY_NORMAL will show all WARNING and higher logs + * - OutputInterface::VERBOSITY_VERBOSE (-v) will show all NOTICE and higher logs + * - OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) will show all INFO and higher logs + * - OutputInterface::VERBOSITY_DEBUG (-vvv) will show all DEBUG and higher logs, i.e. all logs + * + * This mapping can be customized with the $verbosityLevelMap constructor parameter. + * + * @author Tobias Schultze + */ +class ConsoleHandler extends AbstractProcessingHandler implements EventSubscriberInterface +{ + /** + * @var OutputInterface|null + */ + private $output; + + /** + * @var array + */ + private $verbosityLevelMap = array( + OutputInterface::VERBOSITY_NORMAL => Logger::WARNING, + OutputInterface::VERBOSITY_VERBOSE => Logger::NOTICE, + OutputInterface::VERBOSITY_VERY_VERBOSE => Logger::INFO, + OutputInterface::VERBOSITY_DEBUG => Logger::DEBUG, + ); + + /** + * Constructor. + * + * @param OutputInterface|null $output The console output to use (the handler remains disabled when passing null + * until the output is set, e.g. by using console events) + * @param bool $bubble Whether the messages that are handled can bubble up the stack + * @param array $verbosityLevelMap Array that maps the OutputInterface verbosity to a minimum logging + * level (leave empty to use the default mapping) + */ + public function __construct(OutputInterface $output = null, $bubble = true, array $verbosityLevelMap = array()) + { + parent::__construct(Logger::DEBUG, $bubble); + $this->output = $output; + + if ($verbosityLevelMap) { + $this->verbosityLevelMap = $verbosityLevelMap; + } + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $this->updateLevel() && parent::isHandling($record); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + // we have to update the logging level each time because the verbosity of the + // console output might have changed in the meantime (it is not immutable) + return $this->updateLevel() && parent::handle($record); + } + + /** + * Sets the console output to use for printing logs. + * + * @param OutputInterface $output The console output to use + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + } + + /** + * Disables the output. + */ + public function close() + { + $this->output = null; + + parent::close(); + } + + /** + * Before a command is executed, the handler gets activated and the console output + * is set in order to know where to write the logs. + * + * @param ConsoleCommandEvent $event + */ + public function onCommand(ConsoleCommandEvent $event) + { + $this->setOutput($event->getOutput()); + } + + /** + * After a command has been executed, it disables the output. + * + * @param ConsoleTerminateEvent $event + */ + public function onTerminate(ConsoleTerminateEvent $event) + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + ConsoleEvents::COMMAND => array('onCommand', 255), + ConsoleEvents::TERMINATE => array('onTerminate', -255), + ); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if ($record['level'] >= Logger::ERROR && $this->output instanceof ConsoleOutputInterface) { + $this->output->getErrorOutput()->write((string) $record['formatted']); + } else { + $this->output->write((string) $record['formatted']); + } + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new ConsoleFormatter(); + } + + /** + * Updates the logging level based on the verbosity setting of the console output. + * + * @return bool Whether the handler is enabled and verbosity is not set to quiet. + */ + private function updateLevel() + { + if (null === $this->output || OutputInterface::VERBOSITY_QUIET === $verbosity = $this->output->getVerbosity()) { + return false; + } + + if (isset($this->verbosityLevelMap[$verbosity])) { + $this->setLevel($this->verbosityLevelMap[$verbosity]); + } else { + $this->setLevel(Logger::DEBUG); + } + + return true; + } +} diff --git a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php index 2d42235556ce5..3d6a3298e062f 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php @@ -14,7 +14,6 @@ use Monolog\Handler\FirePHPHandler as BaseFirePHPHandler; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\HttpKernelInterface; /** * FirePHPHandler. @@ -38,7 +37,7 @@ class FirePHPHandler extends BaseFirePHPHandler */ public function onKernelResponse(FilterResponseEvent $event) { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + if (!$event->isMasterRequest()) { return; } diff --git a/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php index 29134115f3e6c..37e641c578f80 100644 --- a/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Monolog\Handler; use Monolog\Handler\SwiftMailerHandler as BaseSwiftMailerHandler; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\HttpKernel\Event\PostResponseEvent; /** @@ -43,6 +44,16 @@ public function onKernelTerminate(PostResponseEvent $event) $this->instantFlush = true; } + /** + * After the CLI application has been terminated we will always flush messages + * + * @param ConsoleTerminateEvent $event + */ + public function onCliTerminate(ConsoleTerminateEvent $event) + { + $this->instantFlush = true; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index d5ac5a5dc504b..e27c163ae300e 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -13,7 +13,6 @@ use Monolog\Processor\WebProcessor as BaseWebProcessor; use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; /** * WebProcessor override to read from the HttpFoundation's Request @@ -30,7 +29,7 @@ public function __construct() public function onKernelRequest(GetResponseEvent $event) { - if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) { + if ($event->isMasterRequest()) { $this->serverData = $event->getRequest()->server->all(); } } diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php new file mode 100644 index 0000000000000..7eb5234d3dd71 --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Tests\Handler; + +use Monolog\Logger; +use Symfony\Bridge\Monolog\Handler\ConsoleHandler; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Console\Command\Command; + +/** + * Tests the ConsoleHandler and also the ConsoleFormatter. + * + * @author Tobias Schultze + */ +class ConsoleHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $handler = new ConsoleHandler(null, false); + $this->assertFalse($handler->getBubble(), 'the bubble parameter gets propagated'); + } + + public function testIsHandling() + { + $handler = new ConsoleHandler(); + $this->assertFalse($handler->isHandling(array()), '->isHandling returns false when no output is set'); + } + + /** + * @dataProvider provideVerbosityMappingTests + */ + public function testVerbosityMapping($verbosity, $level, $isHandling, array $map = array()) + { + $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $output + ->expects($this->atLeastOnce()) + ->method('getVerbosity') + ->will($this->returnValue($verbosity)) + ; + $handler = new ConsoleHandler($output, true, $map); + $this->assertSame($isHandling, $handler->isHandling(array('level' => $level)), + '->isHandling returns correct value depending on console verbosity and log level' + ); + } + + public function provideVerbosityMappingTests() + { + return array( + array(OutputInterface::VERBOSITY_QUIET, Logger::ERROR, false), + array(OutputInterface::VERBOSITY_NORMAL, Logger::WARNING, true), + array(OutputInterface::VERBOSITY_NORMAL, Logger::NOTICE, false), + array(OutputInterface::VERBOSITY_VERBOSE, Logger::NOTICE, true), + array(OutputInterface::VERBOSITY_VERBOSE, Logger::INFO, false), + array(OutputInterface::VERBOSITY_VERY_VERBOSE, Logger::INFO, true), + array(OutputInterface::VERBOSITY_VERY_VERBOSE, Logger::DEBUG, false), + array(OutputInterface::VERBOSITY_DEBUG, Logger::DEBUG, true), + array(OutputInterface::VERBOSITY_DEBUG, Logger::EMERGENCY, true), + array(OutputInterface::VERBOSITY_NORMAL, Logger::NOTICE, true, array( + OutputInterface::VERBOSITY_NORMAL => Logger::NOTICE, + )), + array(OutputInterface::VERBOSITY_DEBUG, Logger::NOTICE, true, array( + OutputInterface::VERBOSITY_NORMAL => Logger::NOTICE, + )), + ); + } + + public function testVerbosityChanged() + { + $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $output + ->expects($this->at(0)) + ->method('getVerbosity') + ->will($this->returnValue(OutputInterface::VERBOSITY_QUIET)) + ; + $output + ->expects($this->at(1)) + ->method('getVerbosity') + ->will($this->returnValue(OutputInterface::VERBOSITY_DEBUG)) + ; + $handler = new ConsoleHandler($output); + $this->assertFalse($handler->isHandling(array('level' => Logger::NOTICE)), + 'when verbosity is set to quiet, the handler does not handle the log' + ); + $this->assertTrue($handler->isHandling(array('level' => Logger::NOTICE)), + 'since the verbosity of the output increased externally, the handler is now handling the log' + ); + } + + public function testGetFormatter() + { + $handler = new ConsoleHandler(); + $this->assertInstanceOf('Symfony\Bridge\Monolog\Formatter\ConsoleFormatter', $handler->getFormatter(), + '-getFormatter returns ConsoleFormatter by default' + ); + } + + public function testWritingAndFormatting() + { + $output = $this->getMock('Symfony\Component\Console\Output\ConsoleOutputInterface'); + $output + ->expects($this->any()) + ->method('getVerbosity') + ->will($this->returnValue(OutputInterface::VERBOSITY_DEBUG)) + ; + $output + ->expects($this->once()) + ->method('write') + ->with('[2013-05-29 16:21:54] app.INFO: My info message [] []'."\n") + ; + + $errorOutput = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $errorOutput + ->expects($this->once()) + ->method('write') + ->with('[2013-05-29 16:21:54] app.ERROR: My error message [] []'."\n") + ; + + $output + ->expects($this->any()) + ->method('getErrorOutput') + ->will($this->returnValue($errorOutput)) + ; + + $handler = new ConsoleHandler(null, false); + $handler->setOutput($output); + + $infoRecord = array( + 'message' => 'My info message', + 'context' => array(), + 'level' => Logger::INFO, + 'level_name' => Logger::getLevelName(Logger::INFO), + 'channel' => 'app', + 'datetime' => new \DateTime('2013-05-29 16:21:54'), + 'extra' => array(), + ); + + $this->assertTrue($handler->handle($infoRecord), 'The handler finished handling the log as bubble is false.'); + + $errorRecord = array( + 'message' => 'My error message', + 'context' => array(), + 'level' => Logger::ERROR, + 'level_name' => Logger::getLevelName(Logger::ERROR), + 'channel' => 'app', + 'datetime' => new \DateTime('2013-05-29 16:21:54'), + 'extra' => array(), + ); + + $this->assertTrue($handler->handle($errorRecord), 'The handler finished handling the log as bubble is false.'); + } + + public function testLogsFromListeners() + { + $output = new BufferedOutput(); + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + + $handler = new ConsoleHandler(null, false); + + $logger = new Logger('app'); + $logger->pushHandler($handler); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(ConsoleEvents::COMMAND, function () use ($logger) { + $logger->addInfo('Before command message.'); + }); + $dispatcher->addListener(ConsoleEvents::TERMINATE, function () use ($logger) { + $logger->addInfo('Before terminate message.'); + }); + + $dispatcher->addSubscriber($handler); + + $dispatcher->addListener(ConsoleEvents::COMMAND, function () use ($logger) { + $logger->addInfo('After command message.'); + }); + $dispatcher->addListener(ConsoleEvents::TERMINATE, function () use ($logger) { + $logger->addInfo('After terminate message.'); + }); + + $event = new ConsoleCommandEvent(new Command('foo'), $this->getMock('Symfony\Component\Console\Input\InputInterface'), $output); + $dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + $this->assertContains('Before command message.', $out = $output->fetch()); + $this->assertContains('After command message.', $out); + + $event = new ConsoleTerminateEvent(new Command('foo'), $this->getMock('Symfony\Component\Console\Input\InputInterface'), $output, 0); + $dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + $this->assertContains('Before terminate message.', $out = $output->fetch()); + $this->assertContains('After terminate message.', $out); + } +} diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php index dbc45cd4df937..1022f78c1fa3c 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php @@ -14,17 +14,9 @@ use Monolog\Logger; use Symfony\Bridge\Monolog\Processor\WebProcessor; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\HttpKernelInterface; class WebProcessorTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Monolog\\Logger')) { - $this->markTestSkipped('Monolog is not available.'); - } - } - public function testUsesRequestServerData() { $server = array( @@ -42,8 +34,8 @@ public function testUsesRequestServerData() ->disableOriginalConstructor() ->getMock(); $event->expects($this->any()) - ->method('getRequestType') - ->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); + ->method('isMasterRequest') + ->will($this->returnValue(true)); $event->expects($this->any()) ->method('getRequest') ->will($this->returnValue($request)); diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 643bd9229a080..c6cc58b4217bb 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -17,9 +17,18 @@ ], "require": { "php": ">=5.3.3", - "symfony/http-kernel": "~2.2", "monolog/monolog": "~1.3" }, + "require-dev": { + "symfony/http-kernel": "~2.4", + "symfony/console": "~2.4", + "symfony/event-dispatcher": "~2.2" + }, + "suggest": { + "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", + "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it.", + "symfony/event-dispatcher": "Needed when using log messages in console commands" + }, "autoload": { "psr-0": { "Symfony\\Bridge\\Monolog\\": "" } }, @@ -27,7 +36,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php b/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php index 19de6e613f382..70464eb4b57a6 100644 --- a/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php +++ b/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php @@ -79,11 +79,12 @@ class ModelChoiceList extends ObjectChoiceList * Either an array if $choices is given, * or a ModelCriteria to be merged with the $queryObject. * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths. + * @param string $useAsIdentifier a custom unique column (eg slug) to use instead of primary key. * - * @throws MissingOptionsException when no model class is given - * @throws InvalidOptionsException when the model class cannot be found + * @throws MissingOptionsException In case the class parameter is empty. + * @throws InvalidOptionsException In case the query class is not found. */ - public function __construct($class, $labelPath = null, $choices = null, $queryObject = null, $groupPath = null, $preferred = array(), PropertyAccessorInterface $propertyAccessor = null) + public function __construct($class, $labelPath = null, $choices = null, $queryObject = null, $groupPath = null, $preferred = array(), PropertyAccessorInterface $propertyAccessor = null, $useAsIdentifier = null) { $this->class = $class; @@ -98,7 +99,12 @@ public function __construct($class, $labelPath = null, $choices = null, $queryOb $query = new $queryClass(); $this->query = $queryObject ?: $query; - $this->identifier = $this->query->getTableMap()->getPrimaryKeys(); + if ($useAsIdentifier) { + $this->identifier = array( $this->query->getTableMap()->getColumn($useAsIdentifier) ); + } else { + $this->identifier = $this->query->getTableMap()->getPrimaryKeys(); + } + $this->loaded = is_array($choices) || $choices instanceof \Traversable; if ($preferred instanceof ModelCriteria) { @@ -293,6 +299,8 @@ public function getValuesForChoices(array $models) /** * {@inheritdoc} + * + * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForChoices(array $models) { @@ -337,6 +345,8 @@ public function getIndicesForChoices(array $models) /** * {@inheritdoc} + * + * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForValues(array $values) { @@ -435,6 +445,14 @@ private function getIdentifierValues($model) return array(); } + if (1 === count($this->identifier) && current($this->identifier) instanceof \ColumnMap) { + $phpName = current($this->identifier)->getPhpName(); + + if (method_exists($model, 'get'.$phpName)) { + return array($model->{'get'.$phpName}()); + } + } + if ($model instanceof Persistent) { return array($model->getPrimaryKey()); } diff --git a/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php b/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php index 3db34b53e3f72..48c9d310854f2 100644 --- a/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php +++ b/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php @@ -176,5 +176,9 @@ protected function getColumn($class, $property) if ($table && $table->hasColumn($property)) { return $this->cache[$class.'::'.$property] = $table->getColumn($property); } + + if ($table && $table->hasColumnByInsensitiveCase($property)) { + return $this->cache[$class.'::'.$property] = $table->getColumnByInsensitiveCase($property); + } } } diff --git a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php index ca049b5bf28b6..1c2e3cbb5eeb9 100644 --- a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php +++ b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php @@ -79,7 +79,8 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) $options['query'], $options['group_by'], $options['preferred_choices'], - $propertyAccessor + $propertyAccessor, + $options['index_property'] ); }; @@ -94,6 +95,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) 'choice_list' => $choiceList, 'group_by' => null, 'by_reference' => false, + 'index_property' => null, )); } diff --git a/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php b/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php index 6abe32c3ba884..2dff93452e6fb 100644 --- a/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php +++ b/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php @@ -20,7 +20,7 @@ * @author Fabien Potencier * @author William Durand */ -class PropelLogger +class PropelLogger implements \BasicLogger { /** * @var LoggerInterface @@ -30,14 +30,17 @@ class PropelLogger /** * @var array */ - protected $queries; + protected $queries = array(); /** * @var Stopwatch */ protected $stopwatch; - private $isPrepared; + /** + * @var bool + */ + private $isPrepared = false; /** * Constructor. @@ -48,87 +51,59 @@ class PropelLogger public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null) { $this->logger = $logger; - $this->queries = array(); $this->stopwatch = $stopwatch; - $this->isPrepared = false; } /** - * A convenience function for logging an alert event. - * - * @param mixed $message the message to log. + * {@inheritdoc} */ public function alert($message) { - if (null !== $this->logger) { - $this->logger->alert($message); - } + $this->log($message, 'alert'); } /** - * A convenience function for logging a critical event. - * - * @param mixed $message the message to log. + * {@inheritdoc} */ public function crit($message) { - if (null !== $this->logger) { - $this->logger->critical($message); - } + $this->log($message, 'crit'); } /** - * A convenience function for logging an error event. - * - * @param mixed $message the message to log. + * {@inheritdoc} */ public function err($message) { - if (null !== $this->logger) { - $this->logger->error($message); - } + $this->log($message, 'err'); } /** - * A convenience function for logging a warning event. - * - * @param mixed $message the message to log. + * {@inheritdoc} */ public function warning($message) { - if (null !== $this->logger) { - $this->logger->warning($message); - } + $this->log($message, 'warning'); } /** - * A convenience function for logging an critical event. - * - * @param mixed $message the message to log. + * {@inheritdoc} */ public function notice($message) { - if (null !== $this->logger) { - $this->logger->notice($message); - } + $this->log($message, 'notice'); } /** - * A convenience function for logging an critical event. - * - * @param mixed $message the message to log. + * {@inheritdoc} */ public function info($message) { - if (null !== $this->logger) { - $this->logger->info($message); - } + $this->log($message, 'info'); } /** - * A convenience function for logging a debug event. - * - * @param mixed $message the message to log. + * {@inheritdoc} */ public function debug($message) { @@ -152,8 +127,40 @@ public function debug($message) if ($add) { $this->queries[] = $message; - if (null !== $this->logger) { - $this->logger->debug($message); + $this->log($message, 'debug'); + } + } + + /** + * {@inheritdoc} + */ + public function log($message, $severity = null) + { + if (null !== $this->logger) { + $message = is_string($message) ? $message : var_export($message, true); + + switch ($severity) { + case 'alert': + $this->logger->alert($message); + break; + case 'crit': + $this->logger->critical($message); + break; + case 'err': + $this->logger->error($message); + break; + case 'warning': + $this->logger->warning($message); + break; + case 'notice': + $this->logger->notice($message); + break; + case 'info': + $this->logger->info($message); + break; + case 'debug': + default: + $this->logger->debug($message); } } } diff --git a/src/Symfony/Bridge/Propel1/Tests/DataCollector/PropelDataCollectorTest.php b/src/Symfony/Bridge/Propel1/Tests/DataCollector/PropelDataCollectorTest.php index 756cbfa9f417c..7cc2f2c1f85e0 100644 --- a/src/Symfony/Bridge/Propel1/Tests/DataCollector/PropelDataCollectorTest.php +++ b/src/Symfony/Bridge/Propel1/Tests/DataCollector/PropelDataCollectorTest.php @@ -18,13 +18,6 @@ class PropelDataCollectorTest extends Propel1TestCase { - protected function setUp() - { - if (!class_exists('Symfony\Component\HttpFoundation\Request')) { - $this->markTestSkipped('The "HttpFoundation" component is not available'); - } - } - public function testCollectWithoutData() { $c = $this->createCollector(array()); diff --git a/src/Symfony/Bridge/Propel1/Tests/Fixtures/Column.php b/src/Symfony/Bridge/Propel1/Tests/Fixtures/Column.php index 6b03977e20325..24a0ae0e15f36 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Fixtures/Column.php +++ b/src/Symfony/Bridge/Propel1/Tests/Fixtures/Column.php @@ -11,16 +11,17 @@ namespace Symfony\Bridge\Propel1\Tests\Fixtures; -class Column +class Column extends \ColumnMap { private $name; - private $type; + protected $type; public function __construct($name, $type) { $this->name = $name; $this->type = $type; + $this->phpName = ucfirst($name); } public function getType() diff --git a/src/Symfony/Bridge/Propel1/Tests/Fixtures/Item.php b/src/Symfony/Bridge/Propel1/Tests/Fixtures/Item.php index fbe0b82ed101f..14476ef6188b2 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Fixtures/Item.php +++ b/src/Symfony/Bridge/Propel1/Tests/Fixtures/Item.php @@ -23,12 +23,15 @@ class Item implements \Persistent private $price; - public function __construct($id = null, $value = null, $groupName = null, $price = null) + private $slug; + + public function __construct($id = null, $value = null, $groupName = null, $price = null, $slug = null) { $this->id = $id; $this->value = $value; $this->groupName = $groupName; $this->price = $price; + $this->slug = $slug; } public function getId() @@ -56,6 +59,11 @@ public function getPrice() return $this->price; } + public function getSlug() + { + return $this->slug; + } + public function getPrimaryKey() { return $this->getId(); diff --git a/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php b/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php index 220f40525e5c5..db3977f9d18a9 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php +++ b/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php @@ -18,10 +18,16 @@ class ItemQuery 'value' => \PropelColumnTypes::VARCHAR, 'price' => \PropelColumnTypes::FLOAT, 'is_active' => \PropelColumnTypes::BOOLEAN, + 'slug' => \PropelColumnTypes::VARCHAR, 'enabled' => \PropelColumnTypes::BOOLEAN_EMU, 'updated_at' => \PropelColumnTypes::TIMESTAMP, ); + private $caseInsensitiveMap = array( + 'isactive' => 'is_active', + 'updatedat' => 'updated_at', + ); + public static $result = array(); public function find() @@ -68,6 +74,28 @@ public function getColumn($column) } } + /** + * Method from the TableMap API + */ + public function hasColumnByInsensitiveCase($column) + { + $column = strtolower($column); + + return in_array($column, array_keys($this->caseInsensitiveMap)); + } + + /** + * Method from the TableMap API + */ + public function getColumnByInsensitiveCase($column) + { + $column = strtolower($column); + + if (isset($this->caseInsensitiveMap[$column])) { + return $this->getColumn($this->caseInsensitiveMap[$column]); + } + } + /** * Method from the TableMap API */ diff --git a/src/Symfony/Bridge/Propel1/Tests/Form/ChoiceList/ModelChoiceListTest.php b/src/Symfony/Bridge/Propel1/Tests/Form/ChoiceList/ModelChoiceListTest.php index 9f9a69e3319b6..5aecd6e9d123b 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Form/ChoiceList/ModelChoiceListTest.php +++ b/src/Symfony/Bridge/Propel1/Tests/Form/ChoiceList/ModelChoiceListTest.php @@ -259,4 +259,26 @@ public function testInvalidClass() { $choiceList = new ModelChoiceList('Foo\Bar\DoesNotExistClass'); } + + public function testCustomIdentifier() + { + $item1 = new Item(1, 'Foo', null, null, 'slug'); + $item2 = new Item(2, 'Bar', null, null, 'slug2'); + + $choiceList = new ModelChoiceList( + self::ITEM_CLASS, + 'value', + array( + $item1, + $item2, + ), + null, + null, + array(), + null, + 'slug' + ); + + $this->assertSame(array('slug' => $item1, 'slug2' => $item2), $choiceList->getChoices()); + } } diff --git a/src/Symfony/Bridge/Propel1/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php b/src/Symfony/Bridge/Propel1/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php index 0830747608cb7..e4dc79c52edfc 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php +++ b/src/Symfony/Bridge/Propel1/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php @@ -21,12 +21,6 @@ class CollectionToArrayTransformerTest extends Propel1TestCase protected function setUp() { - if (!class_exists('Symfony\Component\Form\Form')) { - $this->markTestSkipped('The "Form" component is not available'); - } - - parent::setUp(); - $this->transformer = new CollectionToArrayTransformer(); } diff --git a/src/Symfony/Bridge/Propel1/Tests/Form/PropelTypeGuesserTest.php b/src/Symfony/Bridge/Propel1/Tests/Form/PropelTypeGuesserTest.php index 3762a576338b5..bb59503e61c63 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Form/PropelTypeGuesserTest.php +++ b/src/Symfony/Bridge/Propel1/Tests/Form/PropelTypeGuesserTest.php @@ -121,6 +121,9 @@ public static function dataProviderForGuessType() array('price', 'number', Guess::MEDIUM_CONFIDENCE), array('updated_at', 'datetime', Guess::HIGH_CONFIDENCE), + array('isActive', 'checkbox', Guess::HIGH_CONFIDENCE), + array('updatedAt', 'datetime', Guess::HIGH_CONFIDENCE), + array('Authors', 'model', Guess::HIGH_CONFIDENCE, true), array('Resellers', 'model', Guess::HIGH_CONFIDENCE, true), array('MainAuthor', 'model', Guess::HIGH_CONFIDENCE, false), diff --git a/src/Symfony/Bridge/Propel1/Tests/Propel1TestCase.php b/src/Symfony/Bridge/Propel1/Tests/Propel1TestCase.php index 3a03391aaed5e..8a55069665940 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Propel1TestCase.php +++ b/src/Symfony/Bridge/Propel1/Tests/Propel1TestCase.php @@ -13,10 +13,4 @@ abstract class Propel1TestCase extends \PHPUnit_Framework_TestCase { - public static function setUpBeforeClass() - { - if (!class_exists('\Propel')) { - self::markTestSkipped('Propel is not available.'); - } - } } diff --git a/src/Symfony/Bridge/Propel1/composer.json b/src/Symfony/Bridge/Propel1/composer.json index 69caa16de63e0..6627072aa392f 100644 --- a/src/Symfony/Bridge/Propel1/composer.json +++ b/src/Symfony/Bridge/Propel1/composer.json @@ -33,7 +33,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php index 998e3773e7018..b96b8b81a29ca 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php @@ -108,6 +108,8 @@ public function __set($name, $value) /** * @param string $name + * + * @return bool */ public function __isset($name) { diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 6bc1c6883b7da..332370c87eb11 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -23,5 +23,5 @@ class ProjectServiceContainer extends Container } } -class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\VirtualProxyInterface {%a}%A \ No newline at end of file diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 9c61d2d98e56b..e4370f8f26b0b 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=5.3.3", "symfony/dependency-injection": "~2.3", - "ocramius/proxy-manager": "~0.3.1" + "ocramius/proxy-manager": "~0.4|~1.0" }, "require-dev": { "symfony/config": "~2.3" @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php b/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php index 2e08c273539e3..932ac6ef3ef9f 100644 --- a/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php +++ b/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php @@ -21,6 +21,9 @@ * * @author Fabien Potencier * @author Clément JOBEILI + * + * @deprecated Deprecated since version 2.4, to be removed in 3.0. Use + * MessageDataCollector of SwiftmailerBundle instead. */ class MessageDataCollector extends DataCollector { diff --git a/src/Symfony/Bridge/Swiftmailer/composer.json b/src/Symfony/Bridge/Swiftmailer/composer.json index 7ad93ab97e3a6..c5a2ae4ee362d 100644 --- a/src/Symfony/Bridge/Swiftmailer/composer.json +++ b/src/Symfony/Bridge/Swiftmailer/composer.json @@ -29,7 +29,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index ad22216e40b87..4be010ba20e56 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,16 @@ CHANGELOG ========= +2.5.0 +----- + + * moved command `twig:lint` from `TwigBundle` + +2.4.0 +----- + + * added stopwatch tag to time templates with the WebProfilerBundle + 2.3.0 ----- diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php new file mode 100644 index 0000000000000..276b6b96723d2 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Command; + +if (!defined('JSON_PRETTY_PRINT')) { + define('JSON_PRETTY_PRINT', 128); +} + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; + +/** + * Command that will validate your template syntax and output encountered errors. + * + * @author Marc Weistroff + * @author Jérôme Tamarelle + */ +class LintCommand extends Command +{ + private $twig; + + /** + * {@inheritdoc} + */ + public function __construct($name = 'twig:lint') + { + parent::__construct($name); + } + + /** + * Sets the twig environment + * + * @param \Twig_Environment $twig + */ + public function setTwigEnvironment(\Twig_Environment $twig) + { + $this->twig = $twig; + } + + /** + * @return \Twig_Environment $twig + */ + protected function getTwigEnvironment() + { + return $this->twig; + } + + protected function configure() + { + $this + ->setDescription('Lints a template and outputs encountered errors') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') + ->addArgument('filename') + ->setHelp(<<%command.name% command lints a template and outputs to STDOUT +the first encountered syntax error. + +You can validate the syntax of a file: + +php %command.full_name% filename + +Or of a whole directory: + +php %command.full_name% dirname +php %command.full_name% dirname --format=json + +You can also pass the template contents from STDIN: + +cat filename | php %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $twig = $this->getTwigEnvironment(); + $filename = $input->getArgument('filename'); + + if (!$filename) { + if (0 !== ftell(STDIN)) { + throw new \RuntimeException("Please provide a filename or pipe template content to STDIN."); + } + + $template = ''; + while (!feof(STDIN)) { + $template .= fread(STDIN, 1024); + } + + return $this->display($input, $output, array($this->validate($twig, $template, uniqid('sf_')))); + } + + $filesInfo = array(); + foreach ($this->findFiles($filename) as $file) { + $filesInfo[] = $this->validate($twig, file_get_contents($file), $file); + } + + return $this->display($input, $output, $filesInfo); + } + + protected function findFiles($filename) + { + if (is_file($filename)) { + return array($filename); + } elseif (is_dir($filename)) { + return Finder::create()->files()->in($filename)->name('*.twig'); + } + + throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); + } + + private function validate(\Twig_Environment $twig, $template, $file) + { + $realLoader = $twig->getLoader(); + try { + $temporaryLoader = new \Twig_Loader_Array(array((string) $file => $template)); + $twig->setLoader($temporaryLoader); + $nodeTree = $twig->parse($twig->tokenize($template, (string) $file)); + $twig->compile($nodeTree); + $twig->setLoader($realLoader); + } catch (\Twig_Error $e) { + $twig->setLoader($realLoader); + + return array('template' => $template, 'file' => $file, 'valid' => false, 'exception' => $e); + } + + return array('template' => $template, 'file' => $file, 'valid' => true); + } + + private function display(InputInterface $input, OutputInterface $output, $files) + { + switch ($input->getOption('format')) { + case 'txt': + return $this->displayTxt($output, $files); + case 'json': + return $this->displayJson($output, $files); + default: + throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); + } + } + + private function displayTxt(OutputInterface $output, $filesInfo) + { + $errors = 0; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $output->isVerbose()) { + $output->writeln('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + $errors++; + $this->renderException($output, $info['template'], $info['exception'], $info['file']); + } + } + + $output->writeln(sprintf('%d/%d valid files', count($filesInfo) - $errors, count($filesInfo))); + + return min($errors, 1); + } + + private function displayJson(OutputInterface $output, $filesInfo) + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + unset($v['template']); + if (!$v['valid']) { + $v['message'] = $v['exception']->getMessage(); + unset($v['exception']); + $errors++; + } + }); + + $output->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT)); + + return min($errors, 1); + } + + private function renderException(OutputInterface $output, $template, \Twig_Error $exception, $file = null) + { + $line = $exception->getTemplateLine(); + + if ($file) { + $output->writeln(sprintf("KO in %s (line %s)", $file, $line)); + } else { + $output->writeln(sprintf("KO (line %s)", $line)); + } + + foreach ($this->getContext($template, $line) as $no => $code) { + $output->writeln(sprintf( + "%s %-6s %s", + $no == $line ? '>>' : ' ', + $no, + $code + )); + if ($no == $line) { + $output->writeln(sprintf('>> %s ', $exception->getRawMessage())); + } + } + } + + private function getContext($template, $line, $context = 3) + { + $lines = explode("\n", $template); + + $position = max(0, $line - $context); + $max = min(count($lines), $line - 1 + $context); + + $result = array(); + while ($position < $max) { + $result[$position + 1] = $lines[$position]; + $position++; + } + + return $result; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php new file mode 100644 index 0000000000000..6b30a279419b7 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * ExpressionExtension gives a way to create Expressions from a template. + * + * @author Fabien Potencier + */ +class ExpressionExtension extends \Twig_Extension +{ + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new \Twig_SimpleFunction('expression', array($this, 'createExpression')), + ); + } + + public function createExpression($expression) + { + return new Expression($expression); + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'expression'; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php new file mode 100644 index 0000000000000..1055b5bac8db8 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.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\Twig\Extension; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser; + +/** + * Twig extension for the stopwatch helper. + * + * @author Wouter J + */ +class StopwatchExtension extends \Twig_Extension +{ + private $stopwatch; + + public function __construct(Stopwatch $stopwatch = null) + { + $this->stopwatch = $stopwatch; + } + + public function getStopwatch() + { + return $this->stopwatch; + } + + public function getTokenParsers() + { + return array( + /* + * {% stopwatch foo %} + * Some stuff which will be recorded on the timeline + * {% endstopwatch %} + */ + new StopwatchTokenParser($this->stopwatch !== null), + ); + } + + public function getName() + { + return 'stopwatch'; + } +} diff --git a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php index 72798d103fa1f..3b47c26ea624b 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php @@ -11,8 +11,11 @@ namespace Symfony\Bridge\Twig\Form; -use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Form\FormRenderer; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; /** * @author Bernhard Schussek @@ -24,9 +27,15 @@ class TwigRenderer extends FormRenderer implements TwigRendererInterface */ private $engine; - public function __construct(TwigRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null) + public function __construct(TwigRendererEngineInterface $engine, $csrfTokenManager = null) { - parent::__construct($engine, $csrfProvider); + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new UnexpectedTypeException($csrfTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); + } + + parent::__construct($engine, $csrfTokenManager); $this->engine = $engine; } diff --git a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php new file mode 100644 index 0000000000000..cc12abd05d06e --- /dev/null +++ b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +/** + * Represents a stopwatch node. + * + * @author Wouter J + */ +class StopwatchNode extends \Twig_Node +{ + public function __construct(\Twig_NodeInterface $name, $body, \Twig_Node_Expression_AssignName $var, $lineno = 0, $tag = null) + { + parent::__construct(array('body' => $body, 'name' => $name, 'var' => $var), array(), $lineno, $tag); + } + + public function compile(\Twig_Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('var')) + ->raw(' = ') + ->subcompile($this->getNode('name')) + ->write(";\n") + ->write("\$this->env->getExtension('stopwatch')->getStopwatch()->start(") + ->subcompile($this->getNode('var')) + ->raw(", 'template');\n") + ->subcompile($this->getNode('body')) + ->write("\$this->env->getExtension('stopwatch')->getStopwatch()->stop(") + ->subcompile($this->getNode('var')) + ->raw(");\n") + ; + } +} diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php index de4dc5ee965a8..635c54ece2387 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php @@ -29,12 +29,12 @@ class Scope /** * @var array */ - private $data; + private $data = array(); /** * @var bool */ - private $left; + private $left = false; /** * @param Scope $parent @@ -42,8 +42,6 @@ class Scope public function __construct(Scope $parent = null) { $this->parent = $parent; - $this->left = false; - $this->data = array(); } /** 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 badfa8492669f..5e85fb40c796e 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 @@ -9,7 +9,7 @@ {%- endblock form_widget %} {% block form_widget_simple -%} - {%- set type = type|default('text') -%} + {% set type = type|default('text') -%} {%- endblock form_widget_simple %} @@ -44,12 +44,12 @@ {% block choice_widget_expanded -%}
- {% for child in form %} + {%- for child in form %} {{- form_widget(child) -}} {{- form_label(child) -}} - {% endfor %} + {% endfor -%}
-{%- endblock choice_widget_expanded %} +{% endblock choice_widget_expanded %} {% block choice_widget_collapsed -%} {% if required and empty_value is none and not empty_value_in_choices and not multiple -%} @@ -95,20 +95,20 @@ {% block datetime_widget -%} {% if widget == 'single_text' %} {{- block('form_widget_simple') -}} - {% else -%} + {% else %}
{{- form_errors(form.date) -}} {{- form_errors(form.time) -}} {{- form_widget(form.date) -}} {{- form_widget(form.time) -}}
- {%- endif %} + {% endif %} {%- endblock datetime_widget %} {% block date_widget -%} - {% if widget == 'single_text' -%} - {{ block('form_widget_simple') }} - {%- else -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else -%}
{{- date_pattern|replace({ '{{ year }}': form_widget(form.year), @@ -120,10 +120,10 @@ {%- endblock date_widget %} {% block time_widget -%} - {% if widget == 'single_text' -%} - {{ block('form_widget_simple') }} - {%- else -%} - {% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else -%} + {% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} %}
{{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %}
@@ -132,8 +132,8 @@ {% block number_widget -%} {# type="number" doesn't work with floats #} - {%- set type = type|default('text') -%} - {{ block('form_widget_simple') }} + {% set type = type|default('text') %} + {{- block('form_widget_simple') -}} {%- endblock number_widget %} {% block integer_widget -%} @@ -146,33 +146,33 @@ {%- endblock money_widget %} {% block url_widget -%} - {% set type = type|default('url') -%} - {{ block('form_widget_simple') }} + {% set type = type|default('url') %} + {{- block('form_widget_simple') -}} {%- endblock url_widget %} {% block search_widget -%} - {% set type = type|default('search') -%} - {{ block('form_widget_simple') }} + {% set type = type|default('search') %} + {{- block('form_widget_simple') -}} {%- endblock search_widget %} {% block percent_widget -%} - {% set type = type|default('text') -%} - {{ block('form_widget_simple') }} % + {% set type = type|default('text') %} + {{- block('form_widget_simple') -}} % {%- endblock percent_widget %} {% block password_widget -%} - {% set type = type|default('password') -%} - {{ block('form_widget_simple') }} + {% set type = type|default('password') %} + {{- block('form_widget_simple') -}} {%- endblock password_widget %} {% block hidden_widget -%} - {% set type = type|default('hidden') -%} - {{ block('form_widget_simple') }} -{%- endblock hidden_widget %} + {% set type = type|default('hidden') %} + {{- block('form_widget_simple') -}} +{%- endblock hidden_widget -%} {% block email_widget -%} - {% set type = type|default('email') -%} - {{ block('form_widget_simple') }} + {% set type = type|default('email') %} + {{- block('form_widget_simple') -}} {%- endblock email_widget %} {% block button_widget -%} @@ -183,26 +183,26 @@ {%- endblock button_widget %} {% block submit_widget -%} - {% set type = type|default('submit') -%} - {{ block('button_widget') }} + {% set type = type|default('submit') %} + {{- block('button_widget') -}} {%- endblock submit_widget %} {% block reset_widget -%} - {% set type = type|default('reset') -%} - {{ block('button_widget') }} + {% set type = type|default('reset') %} + {{- block('button_widget') -}} {%- endblock reset_widget %} {# Labels #} {% block form_label -%} - {% if label is not sameas(false) %} - {%- if not compound -%} + {% if label is not sameas(false) -%} + {% if not compound -%} {% set label_attr = label_attr|merge({'for': id}) %} - {%- endif -%} - {%- if required -%} + {%- endif %} + {% if required -%} {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} - {%- endif -%} - {%- if label is empty -%} + {%- endif %} + {% if label is empty -%} {% set label = name|humanize %} {%- endif -%} {{ label|trans({}, translation_domain) }} @@ -218,7 +218,7 @@ No need to render the errors here, as all errors are mapped to the first child (see RepeatedTypeValidatorExtension). #} - {{- block('form_rows') }} + {{- block('form_rows') -}} {%- endblock repeated_row %} {% block form_row -%} @@ -254,16 +254,16 @@ {%- else -%} {% set form_method = "POST" %} {%- endif -%} -
+ {%- if form_method != method -%} - {%- endif %} + {%- endif -%} {%- endblock form_start %} {% block form_end -%} - {% if not render_rest is defined or render_rest -%} - {{ form_rest(form) }} - {%- endif -%} + {% if not render_rest is defined or render_rest %} + {{- form_rest(form) -}} + {% endif -%}
{%- endblock form_end %} @@ -283,31 +283,61 @@ {% block form_rest -%} {% for child in form -%} - {% if not child.rendered -%} - {{ form_row(child) }} - {%- endif %} + {% if not child.rendered %} + {{- form_row(child) -}} + {% endif %} {%- endfor %} -{%- endblock form_rest %} +{% endblock form_rest %} {# Support #} {% block form_rows -%} - {% for child in form -%} - {{ form_row(child) }} - {%- endfor %} + {% for child in form %} + {{- form_row(child) -}} + {% endfor %} {%- endblock form_rows %} {% block widget_attributes -%} - id="{{ id }}" name="{{ full_name }}"{% if read_only %} readonly="readonly"{% endif %}{% if disabled %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %} - {%- for attrname, attrvalue in attr %} {% if attrname in ['placeholder', 'title'] %}{{ attrname }}="{{ attrvalue|trans({}, translation_domain) }}"{% else %}{{ attrname }}="{{ attrvalue }}"{% endif %}{% endfor %} + id="{{ id }}" name="{{ full_name }}" + {%- if read_only %} readonly="readonly"{% endif -%} + {%- if disabled %} disabled="disabled"{% endif -%} + {%- if required %} required="required"{% endif -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {%- elseif attrvalue is sameas(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not sameas(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} {%- endblock widget_attributes %} {% block widget_container_attributes -%} - {% if id is not empty %}id="{{ id }}" {% endif %} - {%- for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} + {%- if id is not empty %}id="{{ id }}"{% endif -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {%- elseif attrvalue is sameas(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not sameas(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} {%- endblock widget_container_attributes %} {% block button_attributes -%} - id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %} - {%- for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %} + id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {%- elseif attrvalue is sameas(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not sameas(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} {%- endblock button_attributes %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig index d3d7a34f40ddc..5c85128f5a123 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig @@ -19,7 +19,7 @@ {{- form_widget(form) -}} -{%- endblock button_row %} +{% endblock button_row %} {% block hidden_row -%} @@ -27,11 +27,11 @@ {{- form_widget(form) -}} -{%- endblock hidden_row %} +{% endblock hidden_row %} {% block form_widget_compound -%} - {% if form.parent is empty and errors|length > 0 -%} + {%- if form.parent is empty and errors|length > 0 -%}
{{- form_errors(form) -}} diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php new file mode 100644 index 0000000000000..8d5bd991368e2 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Command; + +use Symfony\Bridge\Twig\Command\LintCommand; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Tester\CommandTester; + +/** + * @covers \Symfony\Bridge\Twig\Command\LintCommand + */ +class LintCommandTest extends \PHPUnit_Framework_TestCase +{ + private $files; + + public function testLintCorrectFile() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile('{{ foo }}'); + + $ret = $tester->execute(array('filename' => $filename), array('verbosity' => OutputInterface::VERBOSITY_VERBOSE)); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertRegExp('/^OK in /', $tester->getDisplay()); + } + + public function testLintIncorrectFile() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile('{{ foo'); + + $ret = $tester->execute(array('filename' => $filename)); + + $this->assertEquals(1, $ret, 'Returns 1 in case of error'); + $this->assertRegExp('/^KO in /', $tester->getDisplay()); + } + + /** + * @expectedException \RuntimeException + */ + public function testLintFileNotReadable() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile(''); + unlink($filename); + + $ret = $tester->execute(array('filename' => $filename)); + } + + public function testLintFileCompileTimeException() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile("{{ 2|number_format(2, decimal_point='.', ',') }}"); + + $ret = $tester->execute(array('filename' => $filename)); + + $this->assertEquals(1, $ret, 'Returns 1 in case of error'); + $this->assertRegExp('/^KO in /', $tester->getDisplay()); + } + + /** + * @return CommandTester + */ + private function createCommandTester() + { + $twig = new \Twig_Environment(new \Twig_Loader_Filesystem()); + + $command = new LintCommand(); + $command->setTwigEnvironment($twig); + + $application = new Application(); + $application->add($command); + $command = $application->find('twig:lint'); + + return new CommandTester($command); + } + + /** + * @return string Path to the new file + */ + private function createFile($content) + { + $filename = tempnam(sys_get_temp_dir(), 'sf-'); + file_put_contents($filename, $content); + + $this->files[] = $filename; + + return $filename; + } + + public function setUp() + { + $this->files = array(); + } + + public function tearDown() + { + foreach ($this->files as $file) { + if (file_exists($file)) { + unlink($file); + } + } + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/ExpressionExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/ExpressionExtensionTest.php new file mode 100644 index 0000000000000..749133c65c5a4 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/ExpressionExtensionTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Bridge\Twig\Extension\ExpressionExtension; +use Symfony\Component\ExpressionLanguage\Expression; + +class ExpressionExtensionTest extends \PHPUnit_Framework_TestCase +{ + protected $helper; + + public function testExpressionCreation() + { + $template = "{{ expression('1 == 1') }}"; + $twig = new \Twig_Environment(new \Twig_Loader_String(), array('debug' => true, 'cache' => false, 'autoescape' => true, 'optimizations' => 0)); + $twig->addExtension(new ExpressionExtension()); + + $output = $twig->render($template); + $this->assertEquals('1 == 1', $output); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubFilesystemLoader.php b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubFilesystemLoader.php index 36c61cd6cca08..5a63537a2fa57 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubFilesystemLoader.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/StubFilesystemLoader.php @@ -11,20 +11,13 @@ namespace Symfony\Bridge\Twig\Tests\Extension\Fixtures; -// Preventing autoloader throwing E_FATAL when Twig is now available -if (!class_exists('Twig_Environment')) { - class StubFilesystemLoader +class StubFilesystemLoader extends \Twig_Loader_Filesystem +{ + protected function findTemplate($name) { - } -} else { - class StubFilesystemLoader extends \Twig_Loader_Filesystem - { - protected function findTemplate($name) - { - // strip away bundle name - $parts = explode(':', $name); + // strip away bundle name + $parts = explode(':', $name); - return parent::findTemplate(end($parts)); - } + return parent::findTemplate(end($parts)); } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index f0e2cb3f884fd..4b347fe2efe0e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -30,22 +30,6 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest protected function setUp() { - if (!class_exists('Symfony\Component\Locale\Locale')) { - $this->markTestSkipped('The "Locale" component is not available'); - } - - if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { - $this->markTestSkipped('The "EventDispatcher" component is not available'); - } - - if (!class_exists('Symfony\Component\Form\Form')) { - $this->markTestSkipped('The "Form" component is not available'); - } - - if (!class_exists('Twig_Environment')) { - $this->markTestSkipped('Twig is not available.'); - } - parent::setUp(); $rendererEngine = new TwigRendererEngine(array( diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index 99a782178a9a4..b5a08e47a57dd 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -29,22 +29,6 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest protected function setUp() { - if (!class_exists('Symfony\Component\Locale\Locale')) { - $this->markTestSkipped('The "Locale" component is not available'); - } - - if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { - $this->markTestSkipped('The "EventDispatcher" component is not available'); - } - - if (!class_exists('Symfony\Component\Form\Form')) { - $this->markTestSkipped('The "Form" component is not available'); - } - - if (!class_exists('Twig_Environment')) { - $this->markTestSkipped('Twig is not available.'); - } - parent::setUp(); $rendererEngine = new TwigRendererEngine(array( diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index 077927cd6d5fc..48bebdc13f8f5 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -12,37 +12,41 @@ namespace Symfony\Bridge\Twig\Tests\Extension; use Symfony\Bridge\Twig\Extension\HttpKernelExtension; -use Symfony\Bridge\Twig\Tests\TestCase; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Fragment\FragmentHandler; -class HttpKernelExtensionTest extends TestCase +class HttpKernelExtensionTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - parent::setUp(); - - if (!class_exists('Symfony\Component\HttpKernel\HttpKernel')) { - $this->markTestSkipped('The "HttpKernel" component is not available'); - } - - if (!class_exists('Twig_Environment')) { - $this->markTestSkipped('Twig is not available.'); - } - } - /** * @expectedException \Twig_Error_Runtime */ public function testFragmentWithError() { - $kernel = $this->getFragmentHandler($this->throwException(new \Exception('foo'))); + $renderer = $this->getFragmentHandler($this->throwException(new \Exception('foo'))); - $loader = new \Twig_Loader_Array(array('index' => '{{ fragment("foo") }}')); - $twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false)); - $twig->addExtension(new HttpKernelExtension($kernel)); + $this->renderTemplate($renderer); + } + + public function testRenderFragment() + { + $renderer = $this->getFragmentHandler($this->returnValue(new Response('html'))); + + $response = $this->renderTemplate($renderer); + + $this->assertEquals('html', $response); + } - $this->renderTemplate($kernel); + public function testUnknownFragmentRenderer() + { + $context = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack') + ->disableOriginalConstructor() + ->getMock() + ; + $renderer = new FragmentHandler(array(), false, $context); + + $this->setExpectedException('InvalidArgumentException', 'The "inline" renderer does not exist.'); + $renderer->render('/foo'); } protected function getFragmentHandler($return) @@ -51,8 +55,14 @@ protected function getFragmentHandler($return) $strategy->expects($this->once())->method('getName')->will($this->returnValue('inline')); $strategy->expects($this->once())->method('render')->will($return); - $renderer = new FragmentHandler(array($strategy)); - $renderer->setRequest(Request::create('/')); + $context = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack') + ->disableOriginalConstructor() + ->getMock() + ; + + $context->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $renderer = new FragmentHandler(array($strategy), false, $context); return $renderer; } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php index 3c5d762ca008e..cd0bbdf0ec7d4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php @@ -12,19 +12,9 @@ namespace Symfony\Bridge\Twig\Tests\Extension; use Symfony\Bridge\Twig\Extension\RoutingExtension; -use Symfony\Bridge\Twig\Tests\TestCase; -class RoutingExtensionTest extends TestCase +class RoutingExtensionTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - parent::setUp(); - - if (!class_exists('Symfony\Component\Routing\Route')) { - $this->markTestSkipped('The "Routing" component is not available'); - } - } - /** * @dataProvider getEscapingTemplates */ diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php new file mode 100644 index 0000000000000..bac855390f440 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Bridge\Twig\Extension\StopwatchExtension; + +class StopwatchExtensionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Twig_Error_Syntax + */ + public function testFailIfStoppingWrongEvent() + { + $this->testTiming('{% stopwatch "foo" %}{% endstopwatch "bar" %}', array()); + } + + /** + * @dataProvider getTimingTemplates + */ + public function testTiming($template, $events) + { + $twig = new \Twig_Environment(new \Twig_Loader_String(), array('debug' => true, 'cache' => false, 'autoescape' => true, 'optimizations' => 0)); + $twig->addExtension(new StopwatchExtension($this->getStopwatch($events))); + + try { + $nodes = $twig->render($template); + } catch (\Twig_Error_Runtime $e) { + throw $e->getPrevious(); + } + } + + public function getTimingTemplates() + { + return array( + array('{% stopwatch "foo" %}something{% endstopwatch %}', 'foo'), + array('{% stopwatch "foo" %}symfony2 is fun{% endstopwatch %}{% stopwatch "bar" %}something{% endstopwatch %}', array('foo', 'bar')), + array('{% set foo = "foo" %}{% stopwatch foo %}something{% endstopwatch %}', 'foo'), + array('{% set foo = "foo" %}{% stopwatch foo %}something {% set foo = "bar" %}{% endstopwatch %}', 'foo'), + array('{% stopwatch "foo.bar" %}something{% endstopwatch %}', 'foo.bar'), + array('{% stopwatch "foo" %}something{% endstopwatch %}{% stopwatch "foo" %}something else{% endstopwatch %}', array('foo', 'foo')), + ); + } + + protected function getStopwatch($events = array()) + { + $events = is_array($events) ? $events : array($events); + $stopwatch = $this->getMock('Symfony\Component\Stopwatch\Stopwatch'); + + $i = -1; + foreach ($events as $eventName) { + $stopwatch->expects($this->at(++$i)) + ->method('start') + ->with($this->equalTo($eventName), 'template') + ; + $stopwatch->expects($this->at(++$i)) + ->method('stop') + ->with($this->equalTo($eventName)) + ; + } + + return $stopwatch; + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index 4e03e8a0043ac..777000efd174f 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -15,23 +15,9 @@ use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\Loader\ArrayLoader; -use Symfony\Bridge\Twig\Tests\TestCase; -class TranslationExtensionTest extends TestCase +class TranslationExtensionTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - parent::setUp(); - - if (!class_exists('Symfony\Component\Translation\Translator')) { - $this->markTestSkipped('The "Translation" component is not available'); - } - - if (!class_exists('Twig_Environment')) { - $this->markTestSkipped('Twig is not available.'); - } - } - public function testEscaping() { $output = $this->getTemplate('{% trans %}Percent: %value%%% (%msg%){% endtrans %}')->render(array('value' => 12, 'msg' => 'approx.')); diff --git a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php index a4a4698ed8a50..5f8ae112118fb 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php @@ -11,10 +11,9 @@ namespace Symfony\Bridge\Twig\Tests\Node; -use Symfony\Bridge\Twig\Tests\TestCase; use Symfony\Bridge\Twig\Node\FormThemeNode; -class FormThemeTest extends TestCase +class FormThemeTest extends \PHPUnit_Framework_TestCase { public function testConstructor() { diff --git a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php index 38be27cfbed41..60ef9edfb9751 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -11,10 +11,9 @@ namespace Symfony\Bridge\Twig\Tests\Node; -use Symfony\Bridge\Twig\Tests\TestCase; use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode; -class SearchAndRenderBlockNodeTest extends TestCase +class SearchAndRenderBlockNodeTest extends \PHPUnit_Framework_TestCase { public function testCompileWidget() { diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/ScopeTest.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/ScopeTest.php index bcae5919b597c..aa9d204e5606f 100644 --- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/ScopeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/ScopeTest.php @@ -12,9 +12,8 @@ namespace Symfony\Bridge\Twig\Tests\NodeVisitor; use Symfony\Bridge\Twig\NodeVisitor\Scope; -use Symfony\Bridge\Twig\Tests\TestCase; -class ScopeTest extends TestCase +class ScopeTest extends \PHPUnit_Framework_TestCase { public function testScopeInitiation() { diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php index 24a6215e6772c..65e0dd80184cf 100644 --- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php @@ -13,9 +13,8 @@ use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; -use Symfony\Bridge\Twig\Tests\TestCase; -class TranslationDefaultDomainNodeVisitorTest extends TestCase +class TranslationDefaultDomainNodeVisitorTest extends \PHPUnit_Framework_TestCase { private static $message = 'message'; private static $domain = 'domain'; diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php index 4e3ee6fdfa37f..0f3ec40091d1f 100644 --- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php @@ -12,9 +12,8 @@ namespace Symfony\Bridge\Twig\Tests\NodeVisitor; use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; -use Symfony\Bridge\Twig\Tests\TestCase; -class TranslationNodeVisitorTest extends TestCase +class TranslationNodeVisitorTest extends \PHPUnit_Framework_TestCase { /** @dataProvider getMessagesExtractionTestData */ public function testMessagesExtraction(\Twig_Node $node, array $expectedMessages) diff --git a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php index c844239e533a8..69c46fd220f1d 100644 --- a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php +++ b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php @@ -11,11 +11,10 @@ namespace Symfony\Bridge\Twig\Tests\TokenParser; -use Symfony\Bridge\Twig\Tests\TestCase; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Bridge\Twig\Node\FormThemeNode; -class FormThemeTokenParserTest extends TestCase +class FormThemeTokenParserTest extends \PHPUnit_Framework_TestCase { /** * @dataProvider getTestsForFormTheme diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index d5614e7ec7c64..a483ab857aa53 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -14,17 +14,9 @@ use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Translation\TwigExtractor; use Symfony\Component\Translation\MessageCatalogue; -use Symfony\Bridge\Twig\Tests\TestCase; -class TwigExtractorTest extends TestCase +class TwigExtractorTest extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Symfony\Component\Translation\Translator')) { - $this->markTestSkipped('The "Translation" component is not available'); - } - } - /** * @dataProvider getExtractData */ diff --git a/src/Symfony/Bridge/Twig/Tests/TwigEngineTest.php b/src/Symfony/Bridge/Twig/Tests/TwigEngineTest.php index 1e6a3c4933675..e7047c354d080 100644 --- a/src/Symfony/Bridge/Twig/Tests/TwigEngineTest.php +++ b/src/Symfony/Bridge/Twig/Tests/TwigEngineTest.php @@ -12,8 +12,9 @@ namespace Symfony\Bridge\Twig\Tests; use Symfony\Bridge\Twig\TwigEngine; +use Symfony\Component\Templating\TemplateReference; -class TwigEngineTest extends TestCase +class TwigEngineTest extends \PHPUnit_Framework_TestCase { public function testExistsWithTemplateInstances() { @@ -27,6 +28,7 @@ public function testExistsWithNonExistentTemplates() $engine = $this->getTwig(); $this->assertFalse($engine->exists('foobar')); + $this->assertFalse($engine->exists(new TemplateReference('foorbar'))); } public function testExistsWithTemplateWithSyntaxErrors() @@ -34,6 +36,7 @@ public function testExistsWithTemplateWithSyntaxErrors() $engine = $this->getTwig(); $this->assertTrue($engine->exists('error')); + $this->assertTrue($engine->exists(new TemplateReference('error'))); } public function testExists() @@ -41,6 +44,25 @@ public function testExists() $engine = $this->getTwig(); $this->assertTrue($engine->exists('index')); + $this->assertTrue($engine->exists(new TemplateReference('index'))); + } + + public function testRender() + { + $engine = $this->getTwig(); + + $this->assertSame('foo', $engine->render('index')); + $this->assertSame('foo', $engine->render(new TemplateReference('index'))); + } + + /** + * @expectedException \Twig_Error_Syntax + */ + public function testRenderWithError() + { + $engine = $this->getTwig(); + + $engine->render(new TemplateReference('error')); } protected function getTwig() diff --git a/src/Symfony/Bridge/Twig/TokenParser/StopwatchTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/StopwatchTokenParser.php new file mode 100644 index 0000000000000..2983e4cb6b03b --- /dev/null +++ b/src/Symfony/Bridge/Twig/TokenParser/StopwatchTokenParser.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\StopwatchNode; + +/** + * Token Parser for the stopwatch tag. + * + * @author Wouter J + */ +class StopwatchTokenParser extends \Twig_TokenParser +{ + protected $stopwatchIsAvailable; + + public function __construct($stopwatchIsAvailable) + { + $this->stopwatchIsAvailable = $stopwatchIsAvailable; + } + + public function parse(\Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + // {% stopwatch 'bar' %} + $name = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + // {% endstopwatch %} + $body = $this->parser->subparse(array($this, 'decideStopwatchEnd'), true); + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + if ($this->stopwatchIsAvailable) { + return new StopwatchNode($name, $body, new \Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $lineno, $this->getTag()); + } + + return $body; + } + + public function decideStopwatchEnd(\Twig_Token $token) + { + return $token->test('endstopwatch'); + } + + public function getTag() + { + return 'stopwatch'; + } +} diff --git a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php index fe36436c7189a..c43a0bded0a56 100644 --- a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +++ b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php @@ -56,7 +56,7 @@ public function extract($directory, MessageCatalogue $catalogue) { // load any existing translation files $finder = new Finder(); - $files = $finder->files()->name('*.twig')->in($directory); + $files = $finder->files()->name('*.twig')->sortByName()->in($directory); foreach ($files as $file) { try { $this->extractTemplate(file_get_contents($file->getPathname()), $catalogue); diff --git a/src/Symfony/Bridge/Twig/TwigEngine.php b/src/Symfony/Bridge/Twig/TwigEngine.php index 9483a9d05ad17..3e3257e7fa0f5 100644 --- a/src/Symfony/Bridge/Twig/TwigEngine.php +++ b/src/Symfony/Bridge/Twig/TwigEngine.php @@ -14,6 +14,7 @@ use Symfony\Component\Templating\EngineInterface; use Symfony\Component\Templating\StreamingEngineInterface; use Symfony\Component\Templating\TemplateNameParserInterface; +use Symfony\Component\Templating\TemplateReferenceInterface; /** * This engine knows how to render Twig templates. @@ -38,15 +39,11 @@ public function __construct(\Twig_Environment $environment, TemplateNameParserIn } /** - * Renders a template. + * {@inheritdoc} * - * @param mixed $name A template name - * @param array $parameters An array of parameters to pass to the template + * It also supports \Twig_Template as name parameter. * - * @return string The evaluated template as a string - * - * @throws \InvalidArgumentException if the template does not exist - * @throws \RuntimeException if the template cannot be rendered + * @throws \Twig_Error if something went wrong like a thrown exception while rendering the template */ public function render($name, array $parameters = array()) { @@ -54,12 +51,11 @@ public function render($name, array $parameters = array()) } /** - * Streams a template. + * {@inheritdoc} * - * @param mixed $name A template name or a TemplateReferenceInterface instance - * @param array $parameters An array of parameters to pass to the template + * It also supports \Twig_Template as name parameter. * - * @throws \RuntimeException if the template cannot be rendered + * @throws \Twig_Error if something went wrong like a thrown exception while rendering the template */ public function stream($name, array $parameters = array()) { @@ -67,11 +63,9 @@ public function stream($name, array $parameters = array()) } /** - * Returns true if the template exists. + * {@inheritdoc} * - * @param mixed $name A template name - * - * @return bool true if the template exists, false otherwise + * It also supports \Twig_Template as name parameter. */ public function exists($name) { @@ -82,11 +76,13 @@ public function exists($name) $loader = $this->environment->getLoader(); if ($loader instanceof \Twig_ExistsLoaderInterface) { - return $loader->exists($name); + return $loader->exists((string) $name); } try { - $loader->getSource($name); + // cast possible TemplateReferenceInterface to string because the + // EngineInterface supports them but Twig_LoaderInterface does not + $loader->getSource((string) $name); } catch (\Twig_Error_Loader $e) { return false; } @@ -95,11 +91,9 @@ public function exists($name) } /** - * Returns true if this class is able to render the given template. - * - * @param string $name A template name + * {@inheritdoc} * - * @return bool True if this class supports the given resource, false otherwise + * It also supports \Twig_Template as name parameter. */ public function supports($name) { @@ -115,7 +109,8 @@ public function supports($name) /** * Loads the given template. * - * @param mixed $name A template name or an instance of Twig_Template + * @param string|TemplateReferenceInterface|\Twig_Template $name A template name or an instance of + * TemplateReferenceInterface or \Twig_Template * * @return \Twig_TemplateInterface A \Twig_TemplateInterface instance * @@ -128,7 +123,7 @@ protected function load($name) } try { - return $this->environment->loadTemplate($name); + return $this->environment->loadTemplate((string) $name); } catch (\Twig_Error_Loader $e) { throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index e4314931b536f..fce7130327646 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -17,28 +17,34 @@ ], "require": { "php": ">=5.3.3", - "twig/twig": "~1.12,>=1.12.3" + "symfony/security-csrf": "~2.4", + "twig/twig": "~1.13,>=1.13.1" }, "require-dev": { "symfony/finder": "~2.3", - "symfony/form": "~2.3,>=2.3.5", + "symfony/form": "2.5.*,>=2.5.2", "symfony/http-kernel": "~2.3", "symfony/locale": "~2.0,>=2.0.5", "symfony/routing": "~2.2", "symfony/templating": "~2.1", "symfony/translation": "~2.2", "symfony/yaml": "~2.0,>=2.0.5", - "symfony/security": "~2.0,>=2.0.5" + "symfony/security": "~2.4", + "symfony/stopwatch": "~2.2", + "symfony/console": "~2.4", + "symfony/expression-language": "~2.4" }, "suggest": { "symfony/finder": "", - "symfony/form": "", - "symfony/http-kernel": "", - "symfony/routing": "", - "symfony/templating": "", - "symfony/translation": "", - "symfony/yaml": "", - "symfony/security": "" + "symfony/form": "For using the FormExtension", + "symfony/http-kernel": "For using the HttpKernelExtension", + "symfony/routing": "For using the RoutingExtension", + "symfony/templating": "For using the TwigEngine", + "symfony/translation": "For using the TranslationExtension", + "symfony/yaml": "For using the YamlExtension", + "symfony/security": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/expression-language": "For using the ExpressionExtension" }, "autoload": { "psr-0": { "Symfony\\Bridge\\Twig\\": "" } @@ -47,7 +53,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 11ab65cff4831..25247608e59ea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,23 @@ CHANGELOG ========= +2.5.0 +----- + + * Added `translation:debug` command + * Added `--no-backup` option to `translation:update` command + * Added `config:debug` command + * Added `yaml:lint` command + * Deprecated the `RouterApacheDumperCommand` which will be removed in Symfony 3.0. + +2.4.0 +----- + + * allowed multiple IP addresses in profiler matcher settings + * added stopwatch helper to time templates with the WebProfilerBundle + * added service definition for "security.secure_random" service + * added service definitions for the new Security CSRF sub-component + 2.3.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php index 7c441a8748a4e..07f81f5878ea1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php @@ -55,7 +55,7 @@ public function findAllTemplates() $templates = array(); - foreach ($this->kernel->getBundles() as $name => $bundle) { + foreach ($this->kernel->getBundles() as $bundle) { $templates = array_merge($templates, $this->findTemplatesInBundle($bundle)); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php new file mode 100644 index 0000000000000..21b23c9aa4069 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Extension\Extension; + +/** + * A console command for dumping available configuration reference. + * + * @author Kevin Bond + * @author Wouter J + * @author Grégoire Pineau + */ +abstract class AbstractConfigCommand extends ContainerDebugCommand +{ + protected function listBundles(OutputInterface $output) + { + $output->writeln('Available registered bundles with their extension alias if available:'); + + $table = $this->getHelperSet()->get('table'); + $table->setHeaders(array('Bundle name', 'Extension alias')); + foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { + $extension = $bundle->getContainerExtension(); + $table->addRow(array($bundle->getName(), $extension ? $extension->getAlias() : '')); + } + + $table->render($output); + } + + protected function findExtension($name) + { + $extension = null; + + $bundles = $this->getContainer()->get('kernel')->getBundles(); + + if (preg_match('/Bundle$/', $name)) { + // input is bundle name + + if (isset($bundles[$name])) { + $extension = $bundles[$name]->getContainerExtension(); + } + + if (!$extension) { + throw new \LogicException(sprintf('No extensions with configuration available for "%s"', $name)); + } + } else { + foreach ($bundles as $bundle) { + $extension = $bundle->getContainerExtension(); + + if ($extension && $name === $extension->getAlias()) { + break; + } + + $extension = null; + } + + if (!$extension) { + throw new \LogicException(sprintf('No extension with alias "%s" is enabled', $name)); + } + } + + return $extension; + } + + public function validateConfiguration(Extension $extension, $configuration) + { + if (!$configuration) { + throw new \LogicException(sprintf('The extension with alias "%s" does not have its getConfiguration() method setup', $extension->getAlias())); + } + + if (!$configuration instanceof ConfigurationInterface) { + throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable', get_class($configuration))); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index f60867d771e1d..d482fe1669ac4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -83,7 +83,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $bundlesDir = $targetArg.'/bundles/'; $filesystem->mkdir($bundlesDir, 0777); - $output->writeln(sprintf("Installing assets using the %s option", $input->getOption('symlink') ? 'symlink' : 'hard copy')); + $output->writeln(sprintf('Installing assets as %s', $input->getOption('symlink') ? 'symlinks' : 'hard copies')); foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { if (is_dir($originDir = $bundle->getPath().'/Resources/public')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index 1fea1b91a4e72..6d4f04bce0bd9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -43,6 +43,7 @@ protected function configure() php %command.full_name% --env=dev php %command.full_name% --env=prod --no-debug + EOF ) ; @@ -77,9 +78,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $warmupDir = substr($realCacheDir, 0, -1).'_'; if ($filesystem->exists($warmupDir)) { + if ($output->isVerbose()) { + $output->writeln(' Clearing outdated warmup directory'); + } $filesystem->remove($warmupDir); } + if ($output->isVerbose()) { + $output->writeln(' Warming up cache'); + } $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); $filesystem->rename($realCacheDir, $oldCacheDir); @@ -89,7 +96,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $filesystem->rename($warmupDir, $realCacheDir); } + if ($output->isVerbose()) { + $output->writeln(' Removing old cache directory'); + } + $filesystem->remove($oldCacheDir); + + if ($output->isVerbose()) { + $output->writeln(' Done'); + } } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 71f071105b62c..8e08153d4a964 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -42,6 +42,7 @@ protected function configure() command, too many classes that should be part of the cache are already loaded in memory). Use curl or any other similar tool to warm up the classes cache if you want. + EOF ) ; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php new file mode 100644 index 0000000000000..777628623f924 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Yaml\Yaml; + +/** + * A console command for dumping available configuration reference. + * + * @author Grégoire Pineau + */ +class ConfigDebugCommand extends AbstractConfigCommand +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('config:debug') + ->setDefinition(array( + new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'), + )) + ->setDescription('Dumps the current configuration for an extension') + ->setHelp(<<%command.name% command dumps the current configuration for an +extension/bundle. + +Either the extension alias or bundle name can be used: + + php %command.full_name% framework + php %command.full_name% FrameworkBundle + +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $name = $input->getArgument('name'); + + if (empty($name)) { + $this->listBundles($output); + + return; + } + + $extension = $this->findExtension($name); + + $kernel = $this->getContainer()->get('kernel'); + $method = new \ReflectionMethod($kernel, 'buildContainer'); + $method->setAccessible(true); + $container = $method->invoke($kernel); + + $configs = $container->getExtensionConfig($extension->getAlias()); + $configuration = $extension->getConfiguration($configs, $container); + + $this->validateConfiguration($extension, $configuration); + + $configs = $container->getParameterBag()->resolveValue($configs); + + $processor = new Processor(); + $config = $processor->processConfiguration($configuration, $configs); + + if ($name === $extension->getAlias()) { + $output->writeln(sprintf('# Current configuration for extension with alias: "%s"', $name)); + } else { + $output->writeln(sprintf('# Current configuration for "%s"', $name)); + } + + $output->writeln(Yaml::dump(array($extension->getAlias() => $config), 3)); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index bf118407b0200..4a92fd63a8906 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -11,18 +11,21 @@ namespace Symfony\Bundle\FrameworkBundle\Command; -use Symfony\Component\Config\Definition\ReferenceDumper; +use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; +use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Config\Definition\ConfigurationInterface; /** - * A console command for dumping available configuration reference + * A console command for dumping available configuration reference. * * @author Kevin Bond + * @author Wouter J + * @author Grégoire Pineau */ -class ConfigDumpReferenceCommand extends ContainerDebugCommand +class ConfigDumpReferenceCommand extends AbstractConfigCommand { /** * {@inheritdoc} @@ -32,21 +35,25 @@ protected function configure() $this ->setName('config:dump-reference') ->setDefinition(array( - new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle or extension alias'), + new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The format, either yaml or xml', 'yaml'), )) - ->setDescription('Dumps default configuration for an extension') + ->setDescription('Dumps the default configuration for an extension') ->setHelp(<<%command.name% command dumps the default configuration for an extension/bundle. +The %command.name% command dumps the default configuration for an +extension/bundle. -The extension alias or bundle name can be used: - -Example: +Either the extension alias or bundle name can be used: php %command.full_name% framework + php %command.full_name% FrameworkBundle -or +With the format option specifies the format of the configuration, +this is either yaml or xml. +When the option is not provided, yaml is used. + + php %command.full_name% FrameworkBundle --format=xml - php %command.full_name% FrameworkBundle EOF ) ; @@ -59,66 +66,40 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $bundles = $this->getContainer()->get('kernel')->getBundles(); - $containerBuilder = $this->getContainerBuilder(); - $name = $input->getArgument('name'); if (empty($name)) { - $output->writeln('Available registered bundles with their extension alias if available:'); - foreach ($bundles as $bundle) { - $extension = $bundle->getContainerExtension(); - $output->writeln($bundle->getName().($extension ? ': '.$extension->getAlias() : '')); - } + $this->listBundles($output); return; } - $extension = null; - - if (preg_match('/Bundle$/', $name)) { - // input is bundle name + $extension = $this->findExtension($name); - if (isset($bundles[$name])) { - $extension = $bundles[$name]->getContainerExtension(); - } + $configuration = $extension->getConfiguration(array(), $this->getContainerBuilder()); - if (!$extension) { - throw new \LogicException(sprintf('No extensions with configuration available for "%s"', $name)); - } + $this->validateConfiguration($extension, $configuration); - $message = 'Default configuration for "'.$name.'"'; + if ($name === $extension->getAlias()) { + $message = sprintf('Default configuration for extension with alias: "%s"', $name); } else { - foreach ($bundles as $bundle) { - $extension = $bundle->getContainerExtension(); - - if ($extension && $extension->getAlias() === $name) { - break; - } - - $extension = null; - } - - if (!$extension) { - throw new \LogicException(sprintf('No extension with alias "%s" is enabled', $name)); - } - - $message = 'Default configuration for extension with alias: "'.$name.'"'; + $message = sprintf('Default configuration for "%s"', $name); } - $configuration = $extension->getConfiguration(array(), $containerBuilder); - - if (!$configuration) { - throw new \LogicException(sprintf('The extension with alias "%s" does not have it\'s getConfiguration() method setup', $extension->getAlias())); + switch ($input->getOption('format')) { + case 'yaml': + $output->writeln(sprintf('# %s', $message)); + $dumper = new YamlReferenceDumper(); + break; + case 'xml': + $output->writeln(sprintf('', $message)); + $dumper = new XmlReferenceDumper(); + break; + default: + $output->writeln($message); + throw new \InvalidArgumentException('Only the yaml and xml formats are supported.'); } - if (!$configuration instanceof ConfigurationInterface) { - throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable', get_class($configuration))); - } - - $output->writeln($message); - - $dumper = new ReferenceDumper(); - $output->writeln($dumper->dump($configuration)); + $output->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 17fe850019170..84be032173313 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -11,18 +11,17 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\DependencyInjection\Alias; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; /** - * A console command for retrieving information about services + * A console command for retrieving information about services. * * @author Ryan Weaver */ @@ -42,11 +41,13 @@ protected function configure() ->setName('container:debug') ->setDefinition(array( new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'), - new InputOption('show-private', null, InputOption::VALUE_NONE, 'Use to show public *and* private services'), - new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Show all services with a specific tag'), + new InputOption('show-private', null, InputOption::VALUE_NONE, 'Used to show public *and* private services'), + new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Shows all services with a specific tag'), new InputOption('tags', null, InputOption::VALUE_NONE, 'Displays tagged services for an application'), new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Displays a specific parameter for an application'), new InputOption('parameters', null, InputOption::VALUE_NONE, 'Displays parameters for an application'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output description in other formats', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), )) ->setDescription('Displays current services for an application') ->setHelp(<<php %command.full_name% --parameter=kernel.debug + EOF ) ; @@ -85,57 +87,44 @@ protected function configure() /** * {@inheritdoc} - * - * @throws \LogicException */ protected function execute(InputInterface $input, OutputInterface $output) { $this->validateInput($input); - $this->containerBuilder = $this->getContainerBuilder(); - if ($input->getOption('parameters')) { - $parameters = $this->getContainerBuilder()->getParameterBag()->all(); - - // Sort parameters alphabetically - ksort($parameters); - - $this->outputParameters($output, $parameters); - - return; - } - - $parameter = $input->getOption('parameter'); - if (null !== $parameter) { - $output->write($this->formatParameter($this->getContainerBuilder()->getParameter($parameter))); - - return; - } - - if ($input->getOption('tags')) { - $this->outputTags($output, $input->getOption('show-private')); - - return; - } - - $tag = $input->getOption('tag'); - if (null !== $tag) { - $serviceIds = array_keys($this->containerBuilder->findTaggedServiceIds($tag)); + $object = $this->getContainerBuilder()->getParameterBag(); + $options = array(); + } elseif ($parameter = $input->getOption('parameter')) { + $object = $this->getContainerBuilder(); + $options = array('parameter' => $parameter); + } elseif ($input->getOption('tags')) { + $object = $this->getContainerBuilder(); + $options = array('group_by' => 'tags', 'show_private' => $input->getOption('show-private')); + } elseif ($tag = $input->getOption('tag')) { + $object = $this->getContainerBuilder(); + $options = array('tag' => $tag, 'show_private' => $input->getOption('show-private')); + } elseif ($name = $input->getArgument('name')) { + $object = $this->getContainerBuilder(); + $options = array('id' => $name); } else { - $serviceIds = $this->containerBuilder->getServiceIds(); + $object = $this->getContainerBuilder(); + $options = array('show_private' => $input->getOption('show-private')); } - // sort so that it reads like an index of services - asort($serviceIds); - - $name = $input->getArgument('name'); - if ($name) { - $this->outputService($output, $name); - } else { - $this->outputServices($output, $serviceIds, $input->getOption('show-private'), $tag); - } + $helper = new DescriptorHelper(); + $options['format'] = $input->getOption('format'); + $options['raw_text'] = $input->getOption('raw'); + $helper->describe($output, $object, $options); } + /** + * Validates input arguments and options. + * + * @param InputInterface $input + * + * @throws \InvalidArgumentException + */ protected function validateInput(InputInterface $input) { $options = array('tags', 'tag', 'parameters', 'parameter'); @@ -155,211 +144,6 @@ protected function validateInput(InputInterface $input) } } - protected function outputServices(OutputInterface $output, $serviceIds, $showPrivate = false, $showTagAttributes = null) - { - // set the label to specify public or public+private - if ($showPrivate) { - $label = 'Public and private services'; - } else { - $label = 'Public services'; - } - if ($showTagAttributes) { - $label .= ' with tag '.$showTagAttributes.''; - } - - $output->writeln($this->getHelper('formatter')->formatSection('container', $label)); - - // loop through to get space needed and filter private services - $maxName = 4; - $maxScope = 6; - $maxTags = array(); - foreach ($serviceIds as $key => $serviceId) { - $definition = $this->resolveServiceDefinition($serviceId); - - if ($definition instanceof Definition) { - // filter out private services unless shown explicitly - if (!$showPrivate && !$definition->isPublic()) { - unset($serviceIds[$key]); - continue; - } - - if (strlen($definition->getScope()) > $maxScope) { - $maxScope = strlen($definition->getScope()); - } - - if (null !== $showTagAttributes) { - $tags = $definition->getTag($showTagAttributes); - foreach ($tags as $tag) { - foreach ($tag as $key => $value) { - if (!isset($maxTags[$key])) { - $maxTags[$key] = strlen($key); - } - if (strlen($value) > $maxTags[$key]) { - $maxTags[$key] = strlen($value); - } - } - } - } - } - - if (strlen($serviceId) > $maxName) { - $maxName = strlen($serviceId); - } - } - $format = '%-'.$maxName.'s '; - $format .= implode("", array_map(function ($length) { return "%-{$length}s "; }, $maxTags)); - $format .= '%-'.$maxScope.'s %s'; - - // the title field needs extra space to make up for comment tags - $format1 = '%-'.($maxName + 19).'s '; - $format1 .= implode("", array_map(function ($length) { return '%-'.($length + 19).'s '; }, $maxTags)); - $format1 .= '%-'.($maxScope + 19).'s %s'; - - $tags = array(); - foreach ($maxTags as $tagName => $length) { - $tags[] = ''.$tagName.''; - } - $output->writeln(vsprintf($format1, $this->buildArgumentsArray('Service Id', 'Scope', 'Class Name', $tags))); - - foreach ($serviceIds as $serviceId) { - $definition = $this->resolveServiceDefinition($serviceId); - - if ($definition instanceof Definition) { - $lines = array(); - if (null !== $showTagAttributes) { - foreach ($definition->getTag($showTagAttributes) as $key => $tag) { - $tagValues = array(); - foreach (array_keys($maxTags) as $tagName) { - $tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : ""; - } - if (0 === $key) { - $lines[] = $this->buildArgumentsArray($serviceId, $definition->getScope(), $definition->getClass(), $tagValues); - } else { - $lines[] = $this->buildArgumentsArray(' "', '', '', $tagValues); - } - } - } else { - $lines[] = $this->buildArgumentsArray($serviceId, $definition->getScope(), $definition->getClass()); - } - - foreach ($lines as $arguments) { - $output->writeln(vsprintf($format, $arguments)); - } - } elseif ($definition instanceof Alias) { - $alias = $definition; - $output->writeln(vsprintf($format, $this->buildArgumentsArray($serviceId, 'n/a', sprintf('alias for %s', (string) $alias), count($maxTags) ? array_fill(0, count($maxTags), "") : array()))); - } else { - // we have no information (happens with "service_container") - $service = $definition; - $output->writeln(vsprintf($format, $this->buildArgumentsArray($serviceId, '', get_class($service), count($maxTags) ? array_fill(0, count($maxTags), "") : array()))); - } - } - } - - protected function buildArgumentsArray($serviceId, $scope, $className, array $tagAttributes = array()) - { - $arguments = array($serviceId); - foreach ($tagAttributes as $tagAttribute) { - $arguments[] = $tagAttribute; - } - $arguments[] = $scope; - $arguments[] = $className; - - return $arguments; - } - - /** - * Renders detailed service information about one service - */ - protected function outputService(OutputInterface $output, $serviceId) - { - $definition = $this->resolveServiceDefinition($serviceId); - - $label = sprintf('Information for service %s', $serviceId); - $output->writeln($this->getHelper('formatter')->formatSection('container', $label)); - $output->writeln(''); - - if ($definition instanceof Definition) { - $output->writeln(sprintf('Service Id %s', $serviceId)); - $output->writeln(sprintf('Class %s', $definition->getClass() ?: "-")); - - $tags = $definition->getTags(); - if (count($tags)) { - $output->writeln('Tags'); - foreach ($tags as $tagName => $tagData) { - foreach ($tagData as $singleTagData) { - $output->writeln(sprintf(' - %-30s (%s)', $tagName, implode(', ', array_map(function ($key, $value) { - return sprintf('%s: %s', $key, $value); - }, array_keys($singleTagData), array_values($singleTagData))))); - } - } - } else { - $output->writeln('Tags -'); - } - - $output->writeln(sprintf('Scope %s', $definition->getScope())); - - $public = $definition->isPublic() ? 'yes' : 'no'; - $output->writeln(sprintf('Public %s', $public)); - - $synthetic = $definition->isSynthetic() ? 'yes' : 'no'; - $output->writeln(sprintf('Synthetic %s', $synthetic)); - - $file = $definition->getFile() ? $definition->getFile() : '-'; - $output->writeln(sprintf('Required File %s', $file)); - } elseif ($definition instanceof Alias) { - $alias = $definition; - $output->writeln(sprintf('This service is an alias for the service %s', (string) $alias)); - } else { - // edge case (but true for "service_container", all we have is the service itself - $service = $definition; - $output->writeln(sprintf('Service Id %s', $serviceId)); - $output->writeln(sprintf('Class %s', get_class($service))); - } - } - - protected function outputParameters(OutputInterface $output, $parameters) - { - $output->writeln($this->getHelper('formatter')->formatSection('container', 'List of parameters')); - - $terminalDimensions = $this->getApplication()->getTerminalDimensions(); - $maxTerminalWidth = $terminalDimensions[0]; - $maxParameterWidth = 0; - $maxValueWidth = 0; - - // Determine max parameter & value length - foreach ($parameters as $parameter => $value) { - $parameterWidth = strlen($parameter); - if ($parameterWidth > $maxParameterWidth) { - $maxParameterWidth = $parameterWidth; - } - - $valueWith = strlen($this->formatParameter($value)); - if ($valueWith > $maxValueWidth) { - $maxValueWidth = $valueWith; - } - } - - $maxValueWidth = min($maxValueWidth, $maxTerminalWidth - $maxParameterWidth - 1); - - $formatTitle = '%-'.($maxParameterWidth + 19).'s %-'.($maxValueWidth + 19).'s'; - $format = '%-'.$maxParameterWidth.'s %-'.$maxValueWidth.'s'; - - $output->writeln(sprintf($formatTitle, 'Parameter', 'Value')); - - foreach ($parameters as $parameter => $value) { - $splits = str_split($this->formatParameter($value), $maxValueWidth); - - foreach ($splits as $index => $split) { - if (0 === $index) { - $output->writeln(sprintf($format, $parameter, $split)); - } else { - $output->writeln(sprintf($format, ' ', $split)); - } - } - } - } - /** * Loads the ContainerBuilder from the cache. * @@ -384,77 +168,4 @@ protected function getContainerBuilder() return $container; } - - /** - * Given an array of service IDs, this returns the array of corresponding - * Definition and Alias objects that those ids represent. - * - * @param string $serviceId The service id to resolve - * - * @return Definition|Alias - */ - protected function resolveServiceDefinition($serviceId) - { - if ($this->containerBuilder->hasDefinition($serviceId)) { - return $this->containerBuilder->getDefinition($serviceId); - } - - // Some service IDs don't have a Definition, they're simply an Alias - if ($this->containerBuilder->hasAlias($serviceId)) { - return $this->containerBuilder->getAlias($serviceId); - } - - // the service has been injected in some special way, just return the service - return $this->containerBuilder->get($serviceId); - } - - /** - * Renders list of tagged services grouped by tag - * - * @param OutputInterface $output - * @param bool $showPrivate - */ - protected function outputTags(OutputInterface $output, $showPrivate = false) - { - $tags = $this->containerBuilder->findTags(); - asort($tags); - - $label = 'Tagged services'; - $output->writeln($this->getHelper('formatter')->formatSection('container', $label)); - - foreach ($tags as $tag) { - $serviceIds = $this->containerBuilder->findTaggedServiceIds($tag); - - foreach ($serviceIds as $serviceId => $attributes) { - $definition = $this->resolveServiceDefinition($serviceId); - if ($definition instanceof Definition) { - if (!$showPrivate && !$definition->isPublic()) { - unset($serviceIds[$serviceId]); - continue; - } - } - } - - if (count($serviceIds) === 0) { - continue; - } - - $output->writeln($this->getHelper('formatter')->formatSection('tag', $tag)); - - foreach ($serviceIds as $serviceId => $attributes) { - $output->writeln($serviceId); - } - - $output->writeln(''); - } - } - - protected function formatParameter($value) - { - if (is_bool($value) || is_array($value) || (null === $value)) { - return json_encode($value); - } - - return $value; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php index c8a17e8904b05..63487b750aa5f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php @@ -21,6 +21,10 @@ /** * RouterApacheDumperCommand. * + * @deprecated Deprecated since version 2.5, to be removed in 3.0. + * The performance gains are minimal and it's very hard to replicate + * the behavior of PHP implementation. + * * @author Fabien Potencier */ class RouterApacheDumperCommand extends ContainerAwareCommand @@ -59,6 +63,7 @@ protected function configure() matching. php %command.full_name% + EOF ) ; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 7bd026c6aed96..4a3717dc7d8a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -11,13 +11,16 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; 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\Routing\RouterInterface; +use Symfony\Component\Routing\Route; /** - * A console command for retrieving information about routes + * A console command for retrieving information about routes. * * @author Fabien Potencier * @author Tobias Schultze @@ -49,12 +52,16 @@ protected function configure() ->setName('router:debug') ->setDefinition(array( new InputArgument('name', InputArgument::OPTIONAL, 'A route name'), + new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output route(s) in other formats', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), )) ->setDescription('Displays current routes for an application') ->setHelp(<<%command.name% displays the configured routes: php %command.full_name% + EOF ) ; @@ -68,125 +75,42 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { $name = $input->getArgument('name'); + $helper = new DescriptorHelper(); if ($name) { - $this->outputRoute($output, $name); + $route = $this->getContainer()->get('router')->getRouteCollection()->get($name); + if (!$route) { + throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); + } + $this->convertController($route); + $helper->describe($output, $route, array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'name' => $name, + )); } else { - $this->outputRoutes($output); - } - } - - protected function outputRoutes(OutputInterface $output, $routes = null) - { - if (null === $routes) { - $routes = $this->getContainer()->get('router')->getRouteCollection()->all(); - } - - $output->writeln($this->getHelper('formatter')->formatSection('router', 'Current routes')); - - $maxName = strlen('name'); - $maxMethod = strlen('method'); - $maxScheme = strlen('scheme'); - $maxHost = strlen('host'); - - foreach ($routes as $name => $route) { - $method = $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'; - $scheme = $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'; - $host = '' !== $route->getHost() ? $route->getHost() : 'ANY'; - $maxName = max($maxName, strlen($name)); - $maxMethod = max($maxMethod, strlen($method)); - $maxScheme = max($maxScheme, strlen($scheme)); - $maxHost = max($maxHost, strlen($host)); - } + $routes = $this->getContainer()->get('router')->getRouteCollection(); - $format = '%-'.$maxName.'s %-'.$maxMethod.'s %-'.$maxScheme.'s %-'.$maxHost.'s %s'; - $formatHeader = '%-'.($maxName + 19).'s %-'.($maxMethod + 19).'s %-'.($maxScheme + 19).'s %-'.($maxHost + 19).'s %s'; - $output->writeln(sprintf($formatHeader, 'Name', 'Method', 'Scheme', 'Host', 'Path')); + foreach ($routes as $route) { + $this->convertController($route); + } - foreach ($routes as $name => $route) { - $method = $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'; - $scheme = $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'; - $host = '' !== $route->getHost() ? $route->getHost() : 'ANY'; - $output->writeln(sprintf($format, $name, $method, $scheme, $host, $route->getPath()), OutputInterface::OUTPUT_RAW); + $helper->describe($output, $routes, array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'show_controllers' => $input->getOption('show-controllers'), + )); } } - /** - * @throws \InvalidArgumentException When route does not exist - */ - protected function outputRoute(OutputInterface $output, $name) + private function convertController(Route $route) { - $route = $this->getContainer()->get('router')->getRouteCollection()->get($name); - if (!$route) { - throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); + $nameParser = $this->getContainer()->get('controller_name_converter'); + if ($route->hasDefault('_controller')) { + try { + $route->setDefault('_controller', $nameParser->build($route->getDefault('_controller'))); + } catch (\InvalidArgumentException $e) { + } } - - $output->writeln($this->getHelper('formatter')->formatSection('router', sprintf('Route "%s"', $name))); - - $method = $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'; - $scheme = $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'; - $host = '' !== $route->getHost() ? $route->getHost() : 'ANY'; - - $output->write('Name '); - $output->writeln($name, OutputInterface::OUTPUT_RAW); - - $output->write('Path '); - $output->writeln($route->getPath(), OutputInterface::OUTPUT_RAW); - - $output->write('Host '); - $output->writeln($host, OutputInterface::OUTPUT_RAW); - - $output->write('Scheme '); - $output->writeln($scheme, OutputInterface::OUTPUT_RAW); - - $output->write('Method '); - $output->writeln($method, OutputInterface::OUTPUT_RAW); - - $output->write('Class '); - $output->writeln(get_class($route), OutputInterface::OUTPUT_RAW); - - $output->write('Defaults '); - $output->writeln($this->formatConfigs($route->getDefaults()), OutputInterface::OUTPUT_RAW); - - $output->write('Requirements '); - // we do not want to show the schemes and methods again that are also in the requirements for BC - $requirements = $route->getRequirements(); - unset($requirements['_scheme'], $requirements['_method']); - $output->writeln($this->formatConfigs($requirements) ?: 'NO CUSTOM', OutputInterface::OUTPUT_RAW); - - $output->write('Options '); - $output->writeln($this->formatConfigs($route->getOptions()), OutputInterface::OUTPUT_RAW); - - $output->write('Path-Regex '); - $output->writeln($route->compile()->getRegex(), OutputInterface::OUTPUT_RAW); - - if (null !== $route->compile()->getHostRegex()) { - $output->write('Host-Regex '); - $output->writeln($route->compile()->getHostRegex(), OutputInterface::OUTPUT_RAW); - } - } - - protected function formatValue($value) - { - if (is_object($value)) { - return sprintf('object(%s)', get_class($value)); - } - - if (is_string($value)) { - return $value; - } - - return preg_replace("/\n\s*/s", '', var_export($value, true)); - } - - private function formatConfigs(array $array) - { - $string = ''; - ksort($array); - foreach ($array as $name => $value) { - $string .= ($string ? "\n".str_repeat(' ', 13) : '').$name.': '.$this->formatValue($value); - } - - return $string; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index 7d1f16696ae4e..72b322bca3589 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -13,6 +13,7 @@ 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\Routing\RouterInterface; @@ -50,12 +51,18 @@ protected function configure() ->setName('router:match') ->setDefinition(array( new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'), + new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Sets the HTTP method'), + new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Sets the URI scheme (usually http or https)'), + new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Sets the URI host'), )) ->setDescription('Helps debug routes by simulating a path info match') ->setHelp(<<%command.name% simulates a path info match: +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 + EOF ) ; @@ -67,12 +74,23 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { $router = $this->getContainer()->get('router'); - $matcher = new TraceableUrlMatcher($router->getRouteCollection(), $router->getContext()); + $context = $router->getContext(); + if (null !== $method = $input->getOption('method')) { + $context->setMethod($method); + } + if (null !== $scheme = $input->getOption('scheme')) { + $context->setScheme($scheme); + } + if (null !== $host = $input->getOption('host')) { + $context->setHost($host); + } + + $matcher = new TraceableUrlMatcher($router->getRouteCollection(), $context); $traces = $matcher->getTraces($input->getArgument('path_info')); $matches = false; - foreach ($traces as $i => $trace) { + 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']))); } elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php index 462ff09ca5b8c..42329ddfaa709 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php @@ -44,7 +44,7 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', 'localhost:8000'), + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null), new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'), )) @@ -72,6 +72,7 @@ protected function configure() "prod". See also: http://www.php.net/manual/en/features.commandline.webserver.php + EOF ) ; @@ -100,21 +101,27 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('Running PHP built-in server in production environment is NOT recommended!'); } + $output->writeln(sprintf("Server running on http://%s\n", $input->getArgument('address'))); + $output->writeln('Quit the server with CONTROL-C.'); + if (null === $builder = $this->createPhpProcessBuilder($input, $output, $env)) { return 1; } - $output->writeln(sprintf("Server running on http://%s\n", $input->getArgument('address'))); - $output->writeln('Quit the server with CONTROL-C.'); - $builder->setWorkingDirectory($documentRoot); $builder->setTimeout(null); $process = $builder->getProcess(); - $process->run(function ($type, $buffer) use ($output) { - if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $callback = function ($type, $buffer) use ($output) { $output->write($buffer); - } - }); + }; + } else { + $callback = null; + $process->disableOutput(); + } + + $process->run($callback); if (!$process->isSuccessful()) { $output->writeln('Built-in server terminated unexpectedly'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php new file mode 100644 index 0000000000000..eff9dc194728a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Translation\Catalogue\MergeOperation; +use Symfony\Component\Console\Helper\Table; +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\Translation\MessageCatalogue; +use Symfony\Component\Translation\Translator; + +/** + * Helps finding unused or missing translation messages in a given locale + * and comparing them with the fallback ones. + * + * @author Florian Voutzinos + */ +class TranslationDebugCommand extends ContainerAwareCommand +{ + const MESSAGE_MISSING = 0; + const MESSAGE_UNUSED = 1; + const MESSAGE_EQUALS_FALLBACK = 2; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('translation:debug') + ->setDefinition(array( + new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), + new InputArgument('bundle', InputArgument::REQUIRED, 'The bundle name'), + 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'), + )) + ->setDescription('Displays translation messages informations') + ->setHelp(<<%command.name% command helps finding unused or missing translation +messages and comparing them with the fallback ones by inspecting the +templates and translation files of a given bundle. + +You can display information about bundle translations in a specific locale: + +php %command.full_name% en AcmeDemoBundle + +You can also specify a translation domain for the search: + +php %command.full_name% --domain=messages en AcmeDemoBundle + +You can only display missing messages: + +php %command.full_name% --only-missing en AcmeDemoBundle + +You can only display unused messages: + +php %command.full_name% --only-unused en AcmeDemoBundle + +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $locale = $input->getArgument('locale'); + $domain = $input->getOption('domain'); + $bundle = $this->getContainer()->get('kernel')->getBundle($input->getArgument('bundle')); + $loader = $this->getContainer()->get('translation.loader'); + + // Extract used messages + $extractedCatalogue = new MessageCatalogue($locale); + $this->getContainer()->get('translation.extractor')->extract($bundle->getPath().'/Resources/views', $extractedCatalogue); + + // Load defined messages + $currentCatalogue = new MessageCatalogue($locale); + if (is_dir($bundle->getPath().'/Resources/translations')) { + $loader->loadMessages($bundle->getPath().'/Resources/translations', $currentCatalogue); + } + + // Merge defined and extracted messages to get all message ids + $mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue); + $allMessages = $mergeOperation->getResult()->all($domain); + if (null !== $domain) { + $allMessages = array($domain => $allMessages); + } + + // No defined or extracted messages + if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) { + $outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale); + + if (null !== $domain) { + $outputMessage .= sprintf(' and domain "%s"', $domain); + } + + $output->writeln($outputMessage); + + return; + } + + // Load the fallback catalogues + $fallbackCatalogues = array(); + $translator = $this->getContainer()->get('translator'); + if ($translator instanceof Translator) { + foreach ($translator->getFallbackLocales() as $fallbackLocale) { + if ($fallbackLocale === $locale) { + continue; + } + + $fallbackCatalogue = new MessageCatalogue($fallbackLocale); + $loader->loadMessages($bundle->getPath().'/Resources/translations', $fallbackCatalogue); + $fallbackCatalogues[] = $fallbackCatalogue; + } + } + + /** @var \Symfony\Component\Console\Helper\Table $table */ + $table = new Table($output); + + // Display header line + $headers = array('State(s)', 'Id', sprintf('Message Preview (%s)', $locale)); + foreach ($fallbackCatalogues as $fallbackCatalogue) { + $headers[] = sprintf('Fallback Message Preview (%s)', $fallbackCatalogue->getLocale()); + } + $table->setHeaders($headers); + + // Iterate all message ids and determine their state + foreach ($allMessages as $domain => $messages) { + foreach (array_keys($messages) as $messageId) { + $value = $currentCatalogue->get($messageId, $domain); + $states = array(); + + if ($extractedCatalogue->defines($messageId, $domain)) { + if (!$currentCatalogue->defines($messageId, $domain)) { + $states[] = self::MESSAGE_MISSING; + } + } elseif ($currentCatalogue->defines($messageId, $domain)) { + $states[] = self::MESSAGE_UNUSED; + } + + if (!in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused') + || !in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) { + continue; + } + + foreach ($fallbackCatalogues as $fallbackCatalogue) { + if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) { + $states[] = self::MESSAGE_EQUALS_FALLBACK; + + break; + } + } + + $row = array($this->formatStates($states), $this->formatId($messageId), $this->sanitizeString($value)); + foreach ($fallbackCatalogues as $fallbackCatalogue) { + $row[] = $this->sanitizeString($fallbackCatalogue->get($messageId, $domain)); + } + + $table->addRow($row); + } + } + + $table->render(); + + $output->writeln(''); + $output->writeln('Legend:'); + $output->writeln(sprintf(' %s Missing message', $this->formatState(self::MESSAGE_MISSING))); + $output->writeln(sprintf(' %s Unused message', $this->formatState(self::MESSAGE_UNUSED))); + $output->writeln(sprintf(' %s Same as the fallback message', $this->formatState(self::MESSAGE_EQUALS_FALLBACK))); + } + + private function formatState($state) + { + if (self::MESSAGE_MISSING === $state) { + return 'x'; + } + + if (self::MESSAGE_UNUSED === $state) { + return 'o'; + } + + if (self::MESSAGE_EQUALS_FALLBACK === $state) { + return '='; + } + + return $state; + } + + private function formatStates(array $states) + { + $result = array(); + foreach ($states as $state) { + $result[] = $this->formatState($state); + } + + return implode(' ', $result); + } + + private function formatId($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 (mb_strlen($string, $encoding) > $length) { + return mb_substr($string, 0, $length - 3, $encoding).'...'; + } + } elseif (strlen($string) > $length) { + return substr($string, 0, $length - 3).'...'; + } + + return $string; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index c9fac59318aaa..10c8b903a24d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -52,6 +52,10 @@ protected function configure() 'force', null, InputOption::VALUE_NONE, 'Should the update be done' ), + new InputOption( + 'no-backup', null, InputOption::VALUE_NONE, + 'Should backup be disabled' + ), new InputOption( 'clean', null, InputOption::VALUE_NONE, 'Should clean not found messages' @@ -66,6 +70,7 @@ protected function configure() php %command.full_name% --dump-messages en AcmeBundle php %command.full_name% --force --prefix="new_" fr AcmeBundle + EOF ) ; @@ -138,6 +143,10 @@ protected function execute(InputInterface $input, OutputInterface $output) } } + if ($input->getOption('no-backup') === true) { + $writer->disableBackup(); + } + // save the files if ($input->getOption('force') === true) { $output->writeln('Writing files'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php new file mode 100644 index 0000000000000..967c3b18a317d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.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\Bundle\FrameworkBundle\Command; + +if (!defined('JSON_PRETTY_PRINT')) { + define('JSON_PRETTY_PRINT', 128); +} + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + */ +class YamlLintCommand extends Command +{ + protected function configure() + { + $this + ->setName('yaml:lint') + ->setDescription('Lints a file and outputs encountered errors') + ->addArgument('filename', null, 'A file or a directory or STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') + ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validate the syntax of a file: + +php %command.full_name% filename + +Or of a whole directory: + +php %command.full_name% dirname +php %command.full_name% dirname --format=json + +Or all YAML files in a bundle: + +php %command.full_name% @AcmeDemoBundle + +You can also pass the YAML contents from STDIN: + +cat filename | php %command.full_name% + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $filename = $input->getArgument('filename'); + + if (!$filename) { + if (0 !== ftell(STDIN)) { + throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $content = ''; + while (!feof(STDIN)) { + $content .= fread(STDIN, 1024); + } + + return $this->display($input, $output, array($this->validate($content))); + } + + if (0 !== strpos($filename, '@') && !is_readable($filename)) { + throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); + } + + $files = array(); + if (is_file($filename)) { + $files = array($filename); + } elseif (is_dir($filename)) { + $files = Finder::create()->files()->in($filename)->name('*.yml'); + } else { + $dir = $this->getApplication()->getKernel()->locateResource($filename); + $files = Finder::create()->files()->in($dir)->name('*.yml'); + } + + $filesInfo = array(); + foreach ($files as $file) { + $filesInfo[] = $this->validate(file_get_contents($file), $file); + } + + return $this->display($input, $output, $filesInfo); + } + + private function validate($content, $file = null) + { + $this->parser = new Parser(); + try { + $this->parser->parse($content); + } catch (ParseException $e) { + return array('file' => $file, 'valid' => false, 'message' => $e->getMessage()); + } + + return array('file' => $file, 'valid' => true); + } + + private function display(InputInterface $input, OutputInterface $output, $files) + { + switch ($input->getOption('format')) { + case 'txt': + return $this->displayTxt($output, $files); + case 'json': + return $this->displayJson($output, $files); + default: + throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); + } + } + + private function displayTxt(OutputInterface $output, $filesInfo) + { + $errors = 0; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $output->isVerbose()) { + $output->writeln('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'])); + } + } + + $output->writeln(sprintf('%d/%d valid files', count($filesInfo) - $errors, count($filesInfo))); + + return min($errors, 1); + } + + private function displayJson(OutputInterface $output, $filesInfo) + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + $errors++; + } + }); + + $output->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT)); + + return min($errors, 1); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 6b02d3c5d2d6a..d10db38f37fa8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -98,10 +98,18 @@ public function doRun(InputInterface $input, OutputInterface $output) protected function registerCommands() { + $container = $this->kernel->getContainer(); + foreach ($this->kernel->getBundles() as $bundle) { if ($bundle instanceof Bundle) { $bundle->registerCommands($this); } } + + if ($container->hasParameter('console.command.ids')) { + foreach ($container->getParameter('console.command.ids') as $id) { + $this->add($container->get($id)); + } + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php new file mode 100644 index 0000000000000..7aaea0b9c3df6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + */ +abstract class Descriptor implements DescriptorInterface +{ + /** + * @var OutputInterface + */ + private $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $this->output = $output; + + switch (true) { + case $object instanceof RouteCollection: + $this->describeRouteCollection($object, $options); + break; + case $object instanceof Route: + $this->describeRoute($object, $options); + break; + case $object instanceof ParameterBag: + $this->describeContainerParameters($object, $options); + break; + case $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by']: + $this->describeContainerTags($object, $options); + break; + case $object instanceof ContainerBuilder && isset($options['id']): + $this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options); + break; + case $object instanceof ContainerBuilder && isset($options['parameter']): + $this->describeContainerParameter($object->getParameter($options['parameter']), $options); + break; + case $object instanceof ContainerBuilder: + $this->describeContainerServices($object, $options); + break; + case $object instanceof Definition: + $this->describeContainerDefinition($object, $options); + break; + case $object instanceof Alias: + $this->describeContainerAlias($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * Writes content to output. + * + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Writes content to output. + * + * @param TableHelper $table + * @param bool $decorated + */ + protected function renderTable(TableHelper $table, $decorated = false) + { + if (!$decorated) { + $table->setCellRowFormat('%s'); + $table->setCellHeaderFormat('%s'); + } + + $table->render($this->output); + } + + /** + * Describes an InputArgument instance. + * + * @param RouteCollection $routes + * @param array $options + */ + abstract protected function describeRouteCollection(RouteCollection $routes, array $options = array()); + + /** + * Describes an InputOption instance. + * + * @param Route $route + * @param array $options + */ + abstract protected function describeRoute(Route $route, array $options = array()); + + /** + * Describes container parameters. + * + * @param ParameterBag $parameters + * @param array $options + */ + abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = array()); + + /** + * Describes container tags. + * + * @param ContainerBuilder $builder + * @param array $options + */ + abstract protected function describeContainerTags(ContainerBuilder $builder, array $options = array()); + + /** + * Describes a container service by its name. + * + * Common options are: + * * name: name of described service + * + * @param Definition|Alias|object $service + * @param array $options + */ + abstract protected function describeContainerService($service, array $options = array()); + + /** + * Describes container services. + * + * Common options are: + * * tag: filters described services by given tag + * + * @param ContainerBuilder $builder + * @param array $options + */ + abstract protected function describeContainerServices(ContainerBuilder $builder, array $options = array()); + + /** + * Describes a service definition. + * + * @param Definition $definition + * @param array $options + */ + abstract protected function describeContainerDefinition(Definition $definition, array $options = array()); + + /** + * Describes a service alias. + * + * @param Alias $alias + * @param array $options + */ + abstract protected function describeContainerAlias(Alias $alias, array $options = array()); + + /** + * Describes a container parameter. + * + * @param string $parameter + * @param array $options + */ + abstract protected function describeContainerParameter($parameter, array $options = array()); + + /** + * Formats a value as string. + * + * @param mixed $value + * + * @return string + */ + protected function formatValue($value) + { + if (is_object($value)) { + return sprintf('object(%s)', get_class($value)); + } + + if (is_string($value)) { + return $value; + } + + return preg_replace("/\n\s*/s", '', var_export($value, true)); + } + + /** + * Formats a parameter. + * + * @param mixed $value + * + * @return string + */ + protected function formatParameter($value) + { + if (is_bool($value) || is_array($value) || (null === $value)) { + $jsonString = json_encode($value); + + if (preg_match('/^(.{60})./us', $jsonString, $matches)) { + return $matches[1].'...'; + } + + return $jsonString; + } + + return (string) $value; + } + + /** + * @param ContainerBuilder $builder + * @param string $serviceId + * + * @return mixed + */ + protected function resolveServiceDefinition(ContainerBuilder $builder, $serviceId) + { + if ($builder->hasDefinition($serviceId)) { + return $builder->getDefinition($serviceId); + } + + // Some service IDs don't have a Definition, they're simply an Alias + if ($builder->hasAlias($serviceId)) { + return $builder->getAlias($serviceId); + } + + // the service has been injected in some special way, just return the service + return $builder->get($serviceId); + } + + /** + * @param ContainerBuilder $builder + * @param bool $showPrivate + * + * @return array + */ + protected function findDefinitionsByTag(ContainerBuilder $builder, $showPrivate) + { + $definitions = array(); + $tags = $builder->findTags(); + asort($tags); + + foreach ($tags as $tag) { + foreach ($builder->findTaggedServiceIds($tag) as $serviceId => $attributes) { + $definition = $this->resolveServiceDefinition($builder, $serviceId); + + if (!$definition instanceof Definition || !$showPrivate && !$definition->isPublic()) { + continue; + } + + if (!isset($definitions[$tag])) { + $definitions[$tag] = array(); + } + + $definitions[$tag][$serviceId] = $definition; + } + } + + return $definitions; + } + + protected function sortParameters(ParameterBag $parameters) + { + $parameters = $parameters->all(); + ksort($parameters); + + return $parameters; + } + + protected function sortServiceIds(array $serviceIds) + { + asort($serviceIds); + + return $serviceIds; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000000000..7748143702fbf --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +if (!defined('JSON_PRETTY_PRINT')) { + define('JSON_PRETTY_PRINT', 128); +} + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeRouteCollection(RouteCollection $routes, array $options = array()) + { + $data = array(); + foreach ($routes->all() as $name => $route) { + $data[$name] = $this->getRouteData($route); + } + + $this->writeData($data, $options); + } + + /** + * {@inheritdoc} + */ + protected function describeRoute(Route $route, array $options = array()) + { + $this->writeData($this->getRouteData($route), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameters(ParameterBag $parameters, array $options = array()) + { + $this->writeData($this->sortParameters($parameters), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerTags(ContainerBuilder $builder, array $options = array()) + { + $showPrivate = isset($options['show_private']) && $options['show_private']; + $data = array(); + + foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + $data[$tag] = array(); + foreach ($definitions as $definition) { + $data[$tag][] = $this->getContainerDefinitionData($definition, true); + } + } + + $this->writeData($data, $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerService($service, array $options = array()) + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + if ($service instanceof Alias) { + $this->writeData($this->getContainerAliasData($service), $options); + } elseif ($service instanceof Definition) { + $this->writeData($this->getContainerDefinitionData($service), $options); + } else { + $this->writeData(get_class($service), $options); + } + } + + /** + * {@inheritdoc} + */ + protected function describeContainerServices(ContainerBuilder $builder, array $options = array()) + { + $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds(); + $showPrivate = isset($options['show_private']) && $options['show_private']; + $data = array('definitions' => array(), 'aliases' => array(), 'services' => array()); + + foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + $service = $this->resolveServiceDefinition($builder, $serviceId); + + if ($service instanceof Alias) { + $data['aliases'][$serviceId] = $this->getContainerAliasData($service); + } elseif ($service instanceof Definition) { + if (($showPrivate || $service->isPublic())) { + $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service); + } + } else { + $data['services'][$serviceId] = get_class($service); + } + } + + $this->writeData($data, $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerDefinition(Definition $definition, array $options = array()) + { + $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags']), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerAlias(Alias $alias, array $options = array()) + { + $this->writeData($this->getContainerAliasData($alias), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameter($parameter, array $options = array()) + { + $key = isset($options['parameter']) ? $options['parameter'] : ''; + + $this->writeData(array($key => $this->formatParameter($parameter)), $options); + } + + /** + * Writes data as json. + * + * @param array $data + * @param array $options + * + * @return array|string + */ + private function writeData(array $data, array $options) + { + $this->write(json_encode($data, (isset($options['json_encoding']) ? $options['json_encoding'] : 0) | JSON_PRETTY_PRINT)."\n"); + } + + /** + * @param Route $route + * + * @return array + */ + protected function getRouteData(Route $route) + { + $requirements = $route->getRequirements(); + unset($requirements['_scheme'], $requirements['_method']); + + return array( + 'path' => $route->getPath(), + 'host' => '' !== $route->getHost() ? $route->getHost() : 'ANY', + 'scheme' => $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY', + 'method' => $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY', + 'class' => get_class($route), + 'defaults' => $route->getDefaults(), + 'requirements' => $requirements ?: 'NO CUSTOM', + 'options' => $route->getOptions(), + 'pathRegex' => $route->compile()->getRegex(), + ); + } + + /** + * @param Definition $definition + * @param bool $omitTags + * + * @return array + */ + private function getContainerDefinitionData(Definition $definition, $omitTags = false) + { + $data = array( + 'class' => (string) $definition->getClass(), + 'scope' => $definition->getScope(), + 'public' => $definition->isPublic(), + 'synthetic' => $definition->isSynthetic(), + 'file' => $definition->getFile(), + ); + + 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); + } + } + } + } + + return $data; + } + + /** + * @param Alias $alias + * + * @return array + */ + private function getContainerAliasData(Alias $alias) + { + return array( + 'service' => (string) $alias, + 'public' => $alias->isPublic(), + ); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000000000..b0cfad0cc5aea --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeRouteCollection(RouteCollection $routes, array $options = array()) + { + $first = true; + foreach ($routes->all() as $name => $route) { + if ($first) { + $first = false; + } else { + $this->write("\n\n"); + } + $this->describeRoute($route, array('name' => $name)); + } + $this->write("\n"); + } + + /** + * {@inheritdoc} + */ + protected function describeRoute(Route $route, array $options = array()) + { + $requirements = $route->getRequirements(); + unset($requirements['_scheme'], $requirements['_method']); + + $output = '- Path: '.$route->getPath() + ."\n".'- Host: '.('' !== $route->getHost() ? $route->getHost() : 'ANY') + ."\n".'- Scheme: '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY') + ."\n".'- Method: '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY') + ."\n".'- Class: '.get_class($route) + ."\n".'- Defaults: '.$this->formatRouterConfig($route->getDefaults()) + ."\n".'- Requirements: '.$this->formatRouterConfig($requirements) ?: 'NONE' + ."\n".'- Options: '.$this->formatRouterConfig($route->getOptions()) + ."\n".'- Path-Regex: '.$route->compile()->getRegex(); + + $this->write(isset($options['name']) + ? $options['name']."\n".str_repeat('-', strlen($options['name']))."\n\n".$output + : $output); + $this->write("\n"); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameters(ParameterBag $parameters, array $options = array()) + { + $this->write("Container parameters\n====================\n"); + foreach ($this->sortParameters($parameters) as $key => $value) { + $this->write(sprintf("\n- `%s`: `%s`", $key, $this->formatParameter($value))); + } + } + + /** + * {@inheritdoc} + */ + protected function describeContainerTags(ContainerBuilder $builder, array $options = array()) + { + $showPrivate = isset($options['show_private']) && $options['show_private']; + $this->write("Container tags\n=============="); + + foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + $this->write("\n\n".$tag."\n".str_repeat('-', strlen($tag))); + foreach ($definitions as $serviceId => $definition) { + $this->write("\n\n"); + $this->describeContainerDefinition($definition, array('omit_tags' => true, 'id' => $serviceId)); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeContainerService($service, array $options = array()) + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + $childOptions = array('id' => $options['id'], 'as_array' => true); + + if ($service instanceof Alias) { + $this->describeContainerAlias($service, $childOptions); + } elseif ($service instanceof Definition) { + $this->describeContainerDefinition($service, $childOptions); + } else { + $this->write(sprintf("**`%s`:** `%s`", $options['id'], get_class($service))); + } + } + + /** + * {@inheritdoc} + */ + protected function describeContainerServices(ContainerBuilder $builder, array $options = array()) + { + $showPrivate = isset($options['show_private']) && $options['show_private']; + + $title = $showPrivate ? 'Public and private services' : 'Public services'; + if (isset($options['tag'])) { + $title .= ' with tag `'.$options['tag'].'`'; + } + $this->write($title."\n".str_repeat('=', strlen($title))); + + $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds(); + $showPrivate = isset($options['show_private']) && $options['show_private']; + $services = array('definitions' => array(), 'aliases' => array(), 'services' => array()); + + foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + $service = $this->resolveServiceDefinition($builder, $serviceId); + + if ($service instanceof Alias) { + $services['aliases'][$serviceId] = $service; + } elseif ($service instanceof Definition) { + if (($showPrivate || $service->isPublic())) { + $services['definitions'][$serviceId] = $service; + } + } else { + $services['services'][$serviceId] = $service; + } + } + + if (!empty($services['definitions'])) { + $this->write("\n\nDefinitions\n-----------\n"); + foreach ($services['definitions'] as $id => $service) { + $this->write("\n"); + $this->describeContainerDefinition($service, array('id' => $id)); + } + } + + if (!empty($services['aliases'])) { + $this->write("\n\nAliases\n-------\n"); + foreach ($services['aliases'] as $id => $service) { + $this->write("\n"); + $this->describeContainerAlias($service, array('id' => $id)); + } + } + + if (!empty($services['services'])) { + $this->write("\n\nServices\n--------\n"); + foreach ($services['services'] as $id => $service) { + $this->write("\n"); + $this->write(sprintf('- `%s`: `%s`', $id, get_class($service))); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeContainerDefinition(Definition $definition, array $options = array()) + { + $output = '- Class: `'.$definition->getClass().'`' + ."\n".'- Scope: `'.$definition->getScope().'`' + ."\n".'- Public: '.($definition->isPublic() ? 'yes' : 'no') + ."\n".'- Synthetic: '.($definition->isSynthetic() ? 'yes' : 'no'); + + if ($definition->getFile()) { + $output .= "\n".'- File: `'.$definition->getFile().'`'; + } + + if (!(isset($options['omit_tags']) && $options['omit_tags'])) { + foreach ($definition->getTags() as $tagName => $tagData) { + foreach ($tagData as $parameters) { + $output .= "\n".'- Tag: `'.$tagName.'`'; + foreach ($parameters as $name => $value) { + $output .= "\n".' - '.ucfirst($name).': '.$value; + } + } + } + } + + $this->write(isset($options['id']) ? sprintf("%s\n%s\n\n%s\n", $options['id'], str_repeat('~', strlen($options['id'])), $output) : $output); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerAlias(Alias $alias, array $options = array()) + { + $output = '- Service: `'.$alias.'`' + ."\n".'- Public: '.($alias->isPublic() ? 'yes' : 'no'); + + $this->write(isset($options['id']) ? sprintf("%s\n%s\n\n%s\n", $options['id'], str_repeat('~', strlen($options['id'])), $output) : $output); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameter($parameter, array $options = array()) + { + $this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter); + } + + private function formatRouterConfig(array $array) + { + if (!count($array)) { + return 'NONE'; + } + + $string = ''; + ksort($array); + foreach ($array as $name => $value) { + $string .= "\n".' - `'.$name.'`: '.$this->formatValue($value); + } + + return $string; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000000000..67cb356bc39ba --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + */ +class TextDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + 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 TableHelper(); + $table->setLayout(TableHelper::LAYOUT_COMPACT); + $table->setHeaders($showControllers ? array_merge($headers, array('Controller')) : $headers); + + foreach ($routes->all() as $name => $route) { + $row = array( + $name, + $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY', + $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY', + '' !== $route->getHost() ? $route->getHost() : 'ANY', + $route->getPath(), + ); + + if ($showControllers) { + $controller = $route->getDefault('_controller'); + if ($controller instanceof \Closure) { + $controller = 'Closure'; + } elseif (is_object($controller)) { + $controller = get_class($controller); + } + $row[] = $controller; + } + + $table->addRow($row); + } + + $this->writeText($this->formatSection('router', 'Current routes')."\n", $options); + $this->renderTable($table, !(isset($options['raw_output']) && $options['raw_output'])); + } + + /** + * {@inheritdoc} + */ + 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(), + 'Host '.('' !== $route->getHost() ? $route->getHost() : 'ANY'), + 'Scheme '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'), + 'Method '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'), + 'Class '.get_class($route), + 'Defaults '.$this->formatRouterConfig($route->getDefaults()), + 'Requirements '.$this->formatRouterConfig($requirements) ?: 'NO CUSTOM', + 'Options '.$this->formatRouterConfig($route->getOptions()), + 'Path-Regex '.$route->compile()->getRegex(), + ); + + if (isset($options['name'])) { + array_unshift($description, 'Name '.$options['name']); + array_unshift($description, $this->formatSection('router', sprintf('Route "%s"', $options['name']))); + } + + if (null !== $route->compile()->getHostRegex()) { + $description[] = 'Host-Regex '.$route->compile()->getHostRegex(); + } + + $this->writeText(implode("\n", $description)."\n", $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameters(ParameterBag $parameters, array $options = array()) + { + $table = new TableHelper(); + $table->setLayout(TableHelper::LAYOUT_COMPACT); + $table->setHeaders(array('Parameter', 'Value')); + + foreach ($this->sortParameters($parameters) as $parameter => $value) { + $table->addRow(array($parameter, $this->formatParameter($value))); + } + + $this->writeText($this->formatSection('container', 'List of parameters')."\n", $options); + $this->renderTable($table, !(isset($options['raw_output']) && $options['raw_output'])); + } + + /** + * {@inheritdoc} + */ + 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[] = ''; + } + + $this->writeText(implode("\n", $description), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerService($service, array $options = array()) + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + if ($service instanceof Alias) { + $this->describeContainerAlias($service, $options); + } 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); + } + } + + /** + * {@inheritdoc} + */ + protected function describeContainerServices(ContainerBuilder $builder, array $options = array()) + { + $showPrivate = isset($options['show_private']) && $options['show_private']; + $showTag = isset($options['tag']) ? $options['tag'] : null; + + if ($showPrivate) { + $label = 'Public and private services'; + } else { + $label = 'Public services'; + } + + if ($showTag) { + $label .= ' with tag '.$options['tag'].''; + } + + $this->writeText($this->formatSection('container', $label)."\n", $options); + + $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds(); + $maxTags = array(); + + foreach ($serviceIds as $key => $serviceId) { + $definition = $this->resolveServiceDefinition($builder, $serviceId); + if ($definition instanceof Definition) { + // filter out private services unless shown explicitly + if (!$showPrivate && !$definition->isPublic()) { + unset($serviceIds[$key]); + continue; + } + if ($showTag) { + $tags = $definition->getTag($showTag); + foreach ($tags as $tag) { + foreach ($tag as $key => $value) { + if (!isset($maxTags[$key])) { + $maxTags[$key] = strlen($key); + } + if (strlen($value) > $maxTags[$key]) { + $maxTags[$key] = strlen($value); + } + } + } + } + } + } + + $tagsCount = count($maxTags); + $tagsNames = array_keys($maxTags); + + $table = new TableHelper(); + $table->setLayout(TableHelper::LAYOUT_COMPACT); + $table->setHeaders(array_merge(array('Service ID'), $tagsNames, array('Scope', 'Class name'))); + + foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + $definition = $this->resolveServiceDefinition($builder, $serviceId); + if ($definition instanceof Definition) { + if ($showTag) { + foreach ($definition->getTag($showTag) as $key => $tag) { + $tagValues = array(); + foreach ($tagsNames as $tagName) { + $tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : ""; + } + if (0 === $key) { + $table->addRow(array_merge(array($serviceId), $tagValues, array($definition->getScope(), $definition->getClass()))); + } else { + $table->addRow(array_merge(array(' "'), $tagValues, array('', ''))); + } + } + } else { + $table->addRow(array($serviceId, $definition->getScope(), $definition->getClass())); + } + } elseif ($definition instanceof Alias) { + $alias = $definition; + $table->addRow(array_merge(array($serviceId, 'n/a', sprintf('alias for "%s"', $alias)), $tagsCount ? array_fill(0, $tagsCount, "") : array())); + } else { + // we have no information (happens with "service_container") + $table->addRow(array_merge(array($serviceId, '', get_class($definition)), $tagsCount ? array_fill(0, $tagsCount, "") : array())); + } + } + + $this->renderTable($table); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerDefinition(Definition $definition, array $options = array()) + { + $description = isset($options['id']) + ? array($this->formatSection('container', sprintf('Information for service %s', $options['id']))) + : array(); + + $description[] = sprintf('Service Id %s', isset($options['id']) ? $options['id'] : '-'); + $description[] = sprintf('Class %s', $definition->getClass() ?: "-"); + + $tags = $definition->getTags(); + if (count($tags)) { + $description[] = 'Tags'; + foreach ($tags as $tagName => $tagData) { + foreach ($tagData as $parameters) { + $description[] = sprintf(' - %-30s (%s)', $tagName, implode(', ', array_map(function ($key, $value) { + return sprintf('%s: %s', $key, $value); + }, array_keys($parameters), array_values($parameters)))); + } + } + } else { + $description[] = 'Tags -'; + } + + $description[] = sprintf('Scope %s', $definition->getScope()); + $description[] = sprintf('Public %s', $definition->isPublic() ? 'yes' : 'no'); + $description[] = sprintf('Synthetic %s', $definition->isSynthetic() ? 'yes' : 'no'); + $description[] = sprintf('Required File %s', $definition->getFile() ? $definition->getFile() : '-'); + + $this->writeText(implode("\n", $description)."\n", $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerAlias(Alias $alias, array $options = array()) + { + $this->writeText(sprintf('This service is an alias for the service %s', (string) $alias), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameter($parameter, array $options = array()) + { + $this->writeText($this->formatParameter($parameter), $options); + } + + /** + * @param array $array + * + * @return string + */ + private function formatRouterConfig(array $array) + { + $string = ''; + ksort($array); + foreach ($array as $name => $value) { + $string .= ($string ? "\n".str_repeat(' ', 13) : '').$name.': '.$this->formatValue($value); + } + + return $string; + } + + /** + * @param string $section + * @param string $message + * + * @return string + */ + private function formatSection($section, $message) + { + return sprintf('[%s] %s', $section, $message); + } + + /** + * @param string $content + * @param array $options + */ + private function writeText($content, array $options = array()) + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000000000..73f693c3f715a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -0,0 +1,396 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jean-François Simon + */ +class XmlDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeRouteCollection(RouteCollection $routes, array $options = array()) + { + $this->writeDocument($this->getRouteCollectionDocument($routes)); + } + + /** + * {@inheritdoc} + */ + protected function describeRoute(Route $route, array $options = array()) + { + $this->writeDocument($this->getRouteDocument($route, isset($options['name']) ? $options['name'] : null)); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameters(ParameterBag $parameters, array $options = array()) + { + $this->writeDocument($this->getContainerParametersDocument($parameters)); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerTags(ContainerBuilder $builder, array $options = array()) + { + $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_private']) && $options['show_private'])); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerService($service, array $options = array()) + { + if (!isset($options['id'])) { + throw new \InvalidArgumentException('An "id" option must be provided.'); + } + + $this->writeDocument($this->getContainerServiceDocument($service, $options['id'])); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerServices(ContainerBuilder $builder, array $options = array()) + { + $this->writeDocument($this->getContainerServicesDocument($builder, isset($options['tag']) ? $options['tag'] : null, isset($options['show_private']) && $options['show_private'])); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerDefinition(Definition $definition, array $options = array()) + { + $this->writeDocument($this->getContainerDefinitionDocument($definition, isset($options['id']) ? $options['id'] : null, isset($options['omit_tags']) && $options['omit_tags'])); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerAlias(Alias $alias, array $options = array()) + { + $this->writeDocument($this->getContainerAliasDocument($alias, isset($options['id']) ? $options['id'] : null)); + } + + /** + * {@inheritdoc} + */ + protected function describeContainerParameter($parameter, array $options = array()) + { + $this->writeDocument($this->getContainerParameterDocument($parameter, $options)); + } + + /** + * Writes DOM document. + * + * @param \DOMDocument $dom + * + * @return \DOMDocument|string + */ + private function writeDocument(\DOMDocument $dom) + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + /** + * @param RouteCollection $routes + * + * @return \DOMDocument + */ + private function getRouteCollectionDocument(RouteCollection $routes) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($routesXML = $dom->createElement('routes')); + + foreach ($routes->all() as $name => $route) { + $routeXML = $this->getRouteDocument($route, $name); + $routesXML->appendChild($routesXML->ownerDocument->importNode($routeXML->childNodes->item(0), true)); + } + + return $dom; + } + + /** + * @param Route $route + * @param string|null $name + * + * @return \DOMDocument + */ + private function getRouteDocument(Route $route, $name = null) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($routeXML = $dom->createElement('route')); + + if ($name) { + $routeXML->setAttribute('name', $name); + } + + $routeXML->setAttribute('class', get_class($route)); + + $routeXML->appendChild($pathXML = $dom->createElement('path')); + $pathXML->setAttribute('regex', $route->compile()->getRegex()); + $pathXML->appendChild(new \DOMText($route->getPath())); + + if ('' !== $route->getHost()) { + $routeXML->appendChild($hostXML = $dom->createElement('host')); + $hostXML->setAttribute('regex', $route->compile()->getHostRegex()); + $hostXML->appendChild(new \DOMText($route->getHost())); + } + + foreach ($route->getSchemes() as $scheme) { + $routeXML->appendChild($schemeXML = $dom->createElement('scheme')); + $schemeXML->appendChild(new \DOMText($scheme)); + } + + foreach ($route->getMethods() as $method) { + $routeXML->appendChild($methodXML = $dom->createElement('method')); + $methodXML->appendChild(new \DOMText($method)); + } + + if (count($route->getDefaults())) { + $routeXML->appendChild($defaultsXML = $dom->createElement('defaults')); + foreach ($route->getDefaults() as $attribute => $value) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->setAttribute('key', $attribute); + $defaultXML->appendChild(new \DOMText($this->formatValue($value))); + } + } + + $requirements = $route->getRequirements(); + unset($requirements['_scheme'], $requirements['_method']); + if (count($requirements)) { + $routeXML->appendChild($requirementsXML = $dom->createElement('requirements')); + foreach ($requirements as $attribute => $pattern) { + $requirementsXML->appendChild($requirementXML = $dom->createElement('requirement')); + $requirementXML->setAttribute('key', $attribute); + $requirementXML->appendChild(new \DOMText($pattern)); + } + } + + if (count($route->getOptions())) { + $routeXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($route->getOptions() as $name => $value) { + $optionsXML->appendChild($optionXML = $dom->createElement('option')); + $optionXML->setAttribute('key', $name); + $optionXML->appendChild(new \DOMText($this->formatValue($value))); + } + } + + return $dom; + } + + /** + * @param ParameterBag $parameters + * + * @return \DOMDocument + */ + private function getContainerParametersDocument(ParameterBag $parameters) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($parametersXML = $dom->createElement('parameters')); + + foreach ($this->sortParameters($parameters) as $key => $value) { + $parametersXML->appendChild($parameterXML = $dom->createElement('parameter')); + $parameterXML->setAttribute('key', $key); + $parameterXML->appendChild(new \DOMText($this->formatParameter($value))); + } + + return $dom; + } + + /** + * @param ContainerBuilder $builder + * @param bool $showPrivate + * + * @return \DOMDocument + */ + private function getContainerTagsDocument(ContainerBuilder $builder, $showPrivate = false) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($containerXML = $dom->createElement('container')); + + foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + $containerXML->appendChild($tagXML = $dom->createElement('tag')); + $tagXML->setAttribute('name', $tag); + + foreach ($definitions as $serviceId => $definition) { + $definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true); + $tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), true)); + } + } + + return $dom; + } + + /** + * @param mixed $service + * @param string $id + * + * @return \DOMDocument + */ + private function getContainerServiceDocument($service, $id) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + if ($service instanceof Alias) { + $dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), true)); + } elseif ($service instanceof Definition) { + $dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id)->childNodes->item(0), true)); + } else { + $dom->appendChild($serviceXML = $dom->createElement('service')); + $serviceXML->setAttribute('id', $id); + $serviceXML->setAttribute('class', get_class($service)); + } + + return $dom; + } + + /** + * @param ContainerBuilder $builder + * @param string|null $tag + * @param bool $showPrivate + * + * @return \DOMDocument + */ + private function getContainerServicesDocument(ContainerBuilder $builder, $tag = null, $showPrivate = false) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($containerXML = $dom->createElement('container')); + + $serviceIds = $tag ? array_keys($builder->findTaggedServiceIds($tag)) : $builder->getServiceIds(); + + foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + $service = $this->resolveServiceDefinition($builder, $serviceId); + + if ($service instanceof Definition && !($showPrivate || $service->isPublic())) { + continue; + } + + $serviceXML = $this->getContainerServiceDocument($service, $serviceId); + $containerXML->appendChild($containerXML->ownerDocument->importNode($serviceXML->childNodes->item(0), true)); + } + + return $dom; + } + + /** + * @param Definition $definition + * @param string|null $id + * @param bool $omitTags + * + * @return \DOMDocument + */ + private function getContainerDefinitionDocument(Definition $definition, $id = null, $omitTags = false) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($serviceXML = $dom->createElement('definition')); + + if ($id) { + $serviceXML->setAttribute('id', $id); + } + + $serviceXML->setAttribute('class', $definition->getClass()); + + if ($definition->getFactoryClass()) { + $serviceXML->setAttribute('factory-class', $definition->getFactoryClass()); + } + + if ($definition->getFactoryService()) { + $serviceXML->setAttribute('factory-service', $definition->getFactoryService()); + } + + if ($definition->getFactoryMethod()) { + $serviceXML->setAttribute('factory-method', $definition->getFactoryMethod()); + } + + $serviceXML->setAttribute('scope', $definition->getScope()); + $serviceXML->setAttribute('public', $definition->isPublic() ? 'true' : 'false'); + $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false'); + $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false'); + $serviceXML->setAttribute('synchronized', $definition->isSynchronized() ? 'true' : 'false'); + $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false'); + $serviceXML->setAttribute('file', $definition->getFile()); + + if (!$omitTags) { + $tags = $definition->getTags(); + + if (count($tags) > 0) { + $serviceXML->appendChild($tagsXML = $dom->createElement('tags')); + foreach ($tags as $tagName => $tagData) { + foreach ($tagData as $parameters) { + $tagsXML->appendChild($tagXML = $dom->createElement('tag')); + $tagXML->setAttribute('name', $tagName); + foreach ($parameters as $name => $value) { + $tagXML->appendChild($parameterXML = $dom->createElement('parameter')); + $parameterXML->setAttribute('name', $name); + $parameterXML->appendChild(new \DOMText($this->formatParameter($value))); + } + } + } + } + } + + return $dom; + } + + /** + * @param Alias $alias + * @param string|null $id + * + * @return \DOMDocument + */ + private function getContainerAliasDocument(Alias $alias, $id = null) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($aliasXML = $dom->createElement('alias')); + + if ($id) { + $aliasXML->setAttribute('id', $id); + } + + $aliasXML->setAttribute('service', (string) $alias); + $aliasXML->setAttribute('public', $alias->isPublic() ? 'true' : 'false'); + + return $dom; + } + + /** + * @param string $parameter + * @param array $options + * + * @return \DOMDocument + */ + private function getContainerParameterDocument($parameter, $options = array()) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($parameterXML = $dom->createElement('parameter')); + + if (isset($options['parameter'])) { + $parameterXML->setAttribute('key', $options['parameter']); + } + + $parameterXML->appendChild(new \DOMText($this->formatParameter($parameter))); + + return $dom; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php b/src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php new file mode 100644 index 0000000000000..acb2a517ab931 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Helper/DescriptorHelper.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Console\Helper; + +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\JsonDescriptor; +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\MarkdownDescriptor; +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor; +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; + +/** + * @author Jean-François Simon + */ +class DescriptorHelper extends BaseDescriptorHelper +{ + /** + * Constructor. + */ + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php index f4f1fbd99d063..a452ac5bb27c2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Form\FormTypeInterface; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormBuilder; @@ -61,7 +62,7 @@ public function generateUrl($route, $parameters = array(), $referenceType = UrlG public function forward($controller, array $path = array(), array $query = array()) { $path['_controller'] = $controller; - $subRequest = $this->container->get('request')->duplicate($query, null, $path); + $subRequest = $this->container->get('request_stack')->getCurrentRequest()->duplicate($query, null, $path); return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); } @@ -139,8 +140,8 @@ public function stream($view, array $parameters = array(), StreamedResponse $res * * throw $this->createNotFoundException('Page not found!'); * - * @param string $message A message - * @param \Exception $previous The previous exception + * @param string $message A message + * @param \Exception|null $previous The previous exception * * @return NotFoundHttpException */ @@ -149,6 +150,23 @@ public function createNotFoundException($message = 'Not Found', \Exception $prev return new NotFoundHttpException($message, $previous); } + /** + * Returns an AccessDeniedException. + * + * This will result in a 403 response code. Usage example: + * + * throw $this->createAccessDeniedException('Unable to access this page!'); + * + * @param string $message A message + * @param \Exception|null $previous The previous exception + * + * @return AccessDeniedException + */ + public function createAccessDeniedException($message = 'Access Denied', \Exception $previous = null) + { + return new AccessDeniedException($message, $previous); + } + /** * Creates and returns a Form instance from the type of the form. * @@ -180,10 +198,14 @@ public function createFormBuilder($data = null, array $options = array()) * Shortcut to return the request service. * * @return Request + * + * @deprecated Deprecated since version 2.4, to be removed in 3.0. Ask + * Symfony to inject the Request object into your controller + * method instead by type hinting it in the method's signature. */ public function getRequest() { - return $this->container->get('request'); + return $this->container->get('request_stack')->getCurrentRequest(); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConsoleCommandPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConsoleCommandPass.php new file mode 100644 index 0000000000000..3c96761ba3a6c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConsoleCommandPass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * AddConsoleCommandPass. + * + * @author Grégoire Pineau + */ +class AddConsoleCommandPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $commandServices = $container->findTaggedServiceIds('console.command'); + + foreach ($commandServices as $id => $tags) { + $definition = $container->getDefinition($id); + + if (!$definition->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be public.', $id)); + } + + if ($definition->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must not be abstract.', $id)); + } + + $class = $container->getParameterBag()->resolveValue($definition->getClass()); + $r = new \ReflectionClass($class); + if (!$r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command')) { + throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must be a subclass of "Symfony\\Component\\Console\\Command\\Command".', $id)); + } + $container->setAlias('console.command.'.strtolower(str_replace('\\', '_', $class)), $id); + } + + $container->setParameter('console.command.ids', array_keys($commandServices)); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php index bf9f33811199a..6f58fc21bebf1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php @@ -19,15 +19,17 @@ class AddValidatorInitializersPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->hasDefinition('validator')) { + if (!$container->hasDefinition('validator.builder')) { return; } + $validatorBuilder = $container->getDefinition('validator.builder'); + $initializers = array(); foreach ($container->findTaggedServiceIds('validator.initializer') as $id => $attributes) { $initializers[] = new Reference($id); } - $container->getDefinition('validator')->replaceArgument(4, $initializers); + $validatorBuilder->addMethodCall('addObjectInitializers', array($initializers)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index c7f8a4de4c752..d430e302fc5c2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -36,7 +36,7 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('secret')->end() ->scalarNode('http_method_override') - ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests.") + ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead") ->defaultTrue() ->end() ->arrayNode('trusted_proxies') @@ -78,12 +78,14 @@ public function getConfigTreeBuilder() ->end() ; + $this->addCsrfSection($rootNode); $this->addFormSection($rootNode); $this->addEsiSection($rootNode); $this->addFragmentsSection($rootNode); $this->addProfilerSection($rootNode); $this->addRouterSection($rootNode); $this->addSessionSection($rootNode); + $this->addRequestSection($rootNode); $this->addTemplatingSection($rootNode); $this->addTranslatorSection($rootNode); $this->addValidationSection($rootNode); @@ -93,6 +95,23 @@ public function getConfigTreeBuilder() return $treeBuilder; } + private function addCsrfSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('csrf_protection') + ->canBeEnabled() + ->children() + ->scalarNode('field_name') + ->defaultValue('_token') + ->info('Deprecated since 2.4, to be removed in 3.0. Use form.csrf_protection.field_name instead') + ->end() + ->end() + ->end() + ->end() + ; + } + private function addFormSection(ArrayNodeDefinition $rootNode) { $rootNode @@ -100,11 +119,17 @@ private function addFormSection(ArrayNodeDefinition $rootNode) ->arrayNode('form') ->info('form configuration') ->canBeEnabled() - ->end() - ->arrayNode('csrf_protection') - ->canBeDisabled() ->children() - ->scalarNode('field_name')->defaultValue('_token')->end() + ->arrayNode('csrf_protection') + ->treatFalseLike(array('enabled' => false)) + ->treatTrueLike(array('enabled' => true)) + ->treatNullLike(array('enabled' => true)) + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('enabled')->defaultNull()->end() // defaults to framework.csrf_protection.enabled + ->scalarNode('field_name')->defaultNull()->end() + ->end() + ->end() ->end() ->end() ->end() @@ -156,13 +181,17 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode) ->arrayNode('matcher') ->canBeUnset() ->performNoDeepMerging() + ->fixXmlConfig('ip') ->children() - ->scalarNode('ip')->end() ->scalarNode('path') ->info('use the urldecoded format') ->example('^/path to resource/') ->end() ->scalarNode('service')->end() + ->arrayNode('ips') + ->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end() + ->prototype('scalar')->end() + ->end() ->end() ->end() ->end() @@ -215,9 +244,42 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->booleanNode('cookie_secure')->end() ->booleanNode('cookie_httponly')->end() ->scalarNode('gc_divisor')->end() - ->scalarNode('gc_probability')->end() + ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() + ->integerNode('metadata_update_threshold') + ->defaultValue('0') + ->info('seconds to wait between 2 session metadata updates, it will also prevent the session handler to write if the session has not changed') + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRequestSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('request') + ->info('request configuration') + ->canBeUnset() + ->fixXmlConfig('format') + ->children() + ->arrayNode('formats') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization() + ->ifTrue(function ($v) { return is_array($v) && isset($v['mime_type']); }) + ->then(function ($v) { return $v['mime_type']; }) + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return !is_array($v); }) + ->then(function ($v) { return array($v); }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() ->end() ->end() ->end() @@ -382,10 +444,43 @@ private function addValidationSection(ArrayNodeDefinition $rootNode) ->children() ->scalarNode('cache')->end() ->booleanNode('enable_annotations')->defaultFalse()->end() + ->arrayNode('static_method') + ->defaultValue(array('loadValidatorMetadata')) + ->prototype('scalar')->end() + ->treatFalseLike(array()) + ->validate() + ->ifTrue(function ($v) { return !is_array($v); }) + ->then(function ($v) { return (array) $v; }) + ->end() + ->end() ->scalarNode('translation_domain')->defaultValue('validators')->end() + ->booleanNode('strict_email')->defaultFalse()->end() + ->enumNode('api') + ->values(array('2.4', '2.5', '2.5-bc', 'auto')) + ->beforeNormalization() + // XML/YAML parse as numbers, not as strings + ->ifTrue(function ($v) { return is_scalar($v); }) + ->then(function ($v) { return (string) $v; }) + ->end() + ->end() ->end() ->end() ->end() + ->validate() + ->ifTrue(function ($v) { return !isset($v['validation']['api']) || 'auto' === $v['validation']['api']; }) + ->then(function ($v) { + // This condition is duplicated in ValidatorBuilder. This + // duplication is necessary in order to know the desired + // API version already during container configuration + // (to adjust service classes etc.) + // See https://github.com/symfony/symfony/issues/11580 + $v['validation']['api'] = PHP_VERSION_ID < 50309 + ? '2.4' + : '2.5-bc'; + + return $v; + }) + ->end() ; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 0988c5fe1b908..8b87c2fd235e7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -12,7 +12,9 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\Config\Resource\FileResource; @@ -20,6 +22,7 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Validator\Validation; /** * FrameworkExtension. @@ -29,6 +32,9 @@ */ class FrameworkExtension extends Extension { + private $formConfigEnabled = false; + private $sessionConfigEnabled = false; + /** * Responds to the app.config configuration parameter. * @@ -48,17 +54,22 @@ public function load(array $configs, ContainerBuilder $container) // will be used and everything will still work as expected. $loader->load('translation.xml'); + // Property access is used by both the Form and the Validator component + $loader->load('property_access.xml'); + $loader->load('debug_prod.xml'); if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); - // only HttpKernel needs the debug event dispatcher $definition = $container->findDefinition('http_kernel'); - $arguments = $definition->getArguments(); - $arguments[0] = new Reference('debug.event_dispatcher'); - $arguments[2] = new Reference('debug.controller_resolver'); - $definition->setArguments($arguments); + $definition->replaceArgument(2, new Reference('debug.controller_resolver')); + + // replace the regular event_dispatcher service with the debug one + $definition = $container->findDefinition('event_dispatcher'); + $definition->setPublic(false); + $container->setDefinition('debug.event_dispatcher.parent', $definition); + $container->setAlias('event_dispatcher', 'debug.event_dispatcher'); } $configuration = $this->getConfiguration($configs, $container); @@ -78,14 +89,32 @@ public function load(array $configs, ContainerBuilder $container) } if (isset($config['session'])) { + $this->sessionConfigEnabled = true; $this->registerSessionConfiguration($config['session'], $container, $loader); } + if (isset($config['request'])) { + $this->registerRequestConfiguration($config['request'], $container, $loader); + } + + $loader->load('security.xml'); + if ($this->isConfigEnabled($container, $config['form'])) { + $this->formConfigEnabled = true; $this->registerFormConfiguration($config, $container, $loader); $config['validation']['enabled'] = true; + + if (!class_exists('Symfony\Component\Validator\Validator')) { + throw new LogicException('The Validator component is required to use the Form component.'); + } + + if ($this->isConfigEnabled($container, $config['form']['csrf_protection'])) { + $config['csrf_protection']['enabled'] = true; + } } + $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); + if (isset($config['templating'])) { $this->registerTemplatingConfiguration($config['templating'], $config['ide'], $container, $loader); } @@ -143,17 +172,20 @@ public function load(array $configs, ContainerBuilder $container) private function registerFormConfiguration($config, ContainerBuilder $container, XmlFileLoader $loader) { $loader->load('form.xml'); - if ($this->isConfigEnabled($container, $config['csrf_protection'])) { - if (!isset($config['session'])) { - throw new \LogicException('CSRF protection needs that sessions are enabled.'); - } - if (!isset($config['secret'])) { - throw new \LogicException('CSRF protection needs a secret to be set.'); - } + if (null === $config['form']['csrf_protection']['enabled']) { + $config['form']['csrf_protection']['enabled'] = $config['csrf_protection']['enabled']; + } + + if ($this->isConfigEnabled($container, $config['form']['csrf_protection'])) { $loader->load('form_csrf.xml'); $container->setParameter('form.type_extension.csrf.enabled', true); - $container->setParameter('form.type_extension.csrf.field_name', $config['csrf_protection']['field_name']); + + if (null !== $config['form']['csrf_protection']['field_name']) { + $container->setParameter('form.type_extension.csrf.field_name', $config['form']['csrf_protection']['field_name']); + } else { + $container->setParameter('form.type_extension.csrf.field_name', $config['csrf_protection']['field_name']); + } } else { $container->setParameter('form.type_extension.csrf.enabled', false); } @@ -210,6 +242,10 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ return; } + if (true === $this->formConfigEnabled) { + $loader->load('form_debug.xml'); + } + $loader->load('profiling.xml'); $loader->load('collectors.xml'); @@ -241,7 +277,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ if (isset($config['matcher'])) { if (isset($config['matcher']['service'])) { $container->setAlias('profiler.request_matcher', $config['matcher']['service']); - } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path'])) { + } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path']) || isset($config['matcher']['ips'])) { $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); $definition->setPublic(false); @@ -249,6 +285,10 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ $definition->addMethodCall('matchIp', array($config['matcher']['ip'])); } + if (isset($config['matcher']['ips'])) { + $definition->addMethodCall('matchIps', array($config['matcher']['ips'])); + } + if (isset($config['matcher']['path'])) { $definition->addMethodCall('matchPath', array($config['matcher']['path'])); } @@ -321,7 +361,14 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $container->getDefinition('session.storage.native')->replaceArgument(1, null); $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); } else { - $container->setAlias('session.handler', $config['handler_id']); + $handlerId = $config['handler_id']; + + if ($config['metadata_update_threshold'] > 0) { + $container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId)); + $handlerId = 'session.handler.write_check'; + } + + $container->setAlias('session.handler', $handlerId); } $container->setParameter('session.save_path', $config['save_path']); @@ -341,6 +388,26 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $container->findDefinition('session.storage')->getClass(), )); } + + $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']); + } + + /** + * Loads the request configuration. + * + * @param array $config A session configuration array + * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance + */ + private function registerRequestConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + { + if ($config['formats']) { + $loader->load('request.xml'); + $container + ->getDefinition('request.add_request_formats_listener') + ->replaceArgument(0, $config['formats']) + ; + } } /** @@ -370,6 +437,15 @@ private function registerTemplatingConfiguration(array $config, $ide, ContainerB if ($container->getParameter('kernel.debug')) { $loader->load('templating_debug.xml'); + $logger = new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE); + + $container->getDefinition('templating.loader.cache') + ->addTag('monolog.logger', array('channel' => 'templating')) + ->addMethodCall('setLogger', array($logger)); + $container->getDefinition('templating.loader.chain') + ->addTag('monolog.logger', array('channel' => 'templating')) + ->addMethodCall('setLogger', array($logger)); + $container->setDefinition('templating.engine.php', $container->findDefinition('debug.templating.engine.php')); $container->setAlias('debug.templating.engine.php', 'templating.engine.php'); } @@ -554,7 +630,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder if (class_exists('Symfony\Component\Security\Core\Exception\AuthenticationException')) { $r = new \ReflectionClass('Symfony\Component\Security\Core\Exception\AuthenticationException'); - $dirs[] = dirname($r->getFilename()).'/../../Resources/translations'; + $dirs[] = dirname($r->getFilename()).'/../Resources/translations'; } $overridePath = $container->getParameter('kernel.root_dir').'/Resources/%s/translations'; foreach ($container->getParameter('kernel.bundles') as $bundle => $class) { @@ -606,25 +682,63 @@ private function registerValidationConfiguration(array $config, ContainerBuilder $loader->load('validator.xml'); + $validatorBuilder = $container->getDefinition('validator.builder'); + $container->setParameter('validator.translation_domain', $config['translation_domain']); - $container->setParameter('validator.mapping.loader.xml_files_loader.mapping_files', $this->getValidatorXmlMappingFiles($container)); - $container->setParameter('validator.mapping.loader.yaml_files_loader.mapping_files', $this->getValidatorYamlMappingFiles($container)); + + $xmlMappings = $this->getValidatorXmlMappingFiles($container); + $yamlMappings = $this->getValidatorYamlMappingFiles($container); + + if (count($xmlMappings) > 0) { + $validatorBuilder->addMethodCall('addXmlMappings', array($xmlMappings)); + } + + if (count($yamlMappings) > 0) { + $validatorBuilder->addMethodCall('addYamlMappings', array($yamlMappings)); + } + + $definition = $container->findDefinition('validator.email'); + $definition->replaceArgument(0, $config['strict_email']); if (array_key_exists('enable_annotations', $config) && $config['enable_annotations']) { - $loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain'); - $arguments = $loaderChain->getArguments(); - array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader')); - $loaderChain->setArguments($arguments); + $validatorBuilder->addMethodCall('enableAnnotationMapping', array(new Reference('annotation_reader'))); + } + + if (array_key_exists('static_method', $config) && $config['static_method']) { + foreach ($config['static_method'] as $methodName) { + $validatorBuilder->addMethodCall('addMethodMapping', array($methodName)); + } } if (isset($config['cache'])) { - $container->getDefinition('validator.mapping.class_metadata_factory') - ->replaceArgument(1, new Reference('validator.mapping.cache.'.$config['cache'])); $container->setParameter( 'validator.mapping.cache.prefix', - 'validator_'.md5($container->getParameter('kernel.root_dir')) + 'validator_'.hash('sha256', $container->getParameter('kernel.root_dir')) ); + + $validatorBuilder->addMethodCall('setMetadataCache', array(new Reference('validator.mapping.cache.'.$config['cache']))); + } + + switch ($config['api']) { + case '2.4': + $api = Validation::API_VERSION_2_4; + break; + case '2.5': + $api = Validation::API_VERSION_2_5; + // the validation class needs to be changed only for the 2.5 api since the deprecated interface is + // set as the default interface + $container->setParameter('validator.class', 'Symfony\Component\Validator\Validator\ValidatorInterface'); + break; + default: + $api = Validation::API_VERSION_2_5_BC; + break; } + + $validatorBuilder->addMethodCall('setApiVersion', array($api)); + + // You can use this parameter to check the API version in your own + // bundle extension classes + $container->setParameter('validator.api', $api); } private function getValidatorXmlMappingFiles(ContainerBuilder $container) @@ -689,6 +803,29 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde } } + /** + * Loads the security configuration. + * + * @param array $config A CSRF configuration array + * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance + * + * @throws \LogicException + */ + private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + { + if (!$this->isConfigEnabled($container, $config)) { + return; + } + + if (!$this->sessionConfigEnabled) { + throw new \LogicException('CSRF protection needs sessions to be enabled.'); + } + + // Enable services for CSRF protection (even without forms) + $loader->load('security_csrf.xml'); + } + /** * Returns the base path for the XSD files. * diff --git a/src/Symfony/Bundle/FrameworkBundle/EventListener/SessionListener.php b/src/Symfony/Bundle/FrameworkBundle/EventListener/SessionListener.php index 014ecb3ebde27..6c248d8a42880 100644 --- a/src/Symfony/Bundle/FrameworkBundle/EventListener/SessionListener.php +++ b/src/Symfony/Bundle/FrameworkBundle/EventListener/SessionListener.php @@ -11,18 +11,15 @@ namespace Symfony\Bundle\FrameworkBundle\EventListener; +use Symfony\Component\HttpKernel\EventListener\SessionListener as BaseSessionListener; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Sets the session in the request. * - * @author Johannes M. Schmitt + * @author Fabien Potencier */ -class SessionListener implements EventSubscriberInterface +class SessionListener extends BaseSessionListener { /** * @var ContainerInterface @@ -34,24 +31,12 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - public function onKernelRequest(GetResponseEvent $event) + protected function getSession() { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + if (!$this->container->has('session')) { return; } - $request = $event->getRequest(); - if (!$this->container->has('session') || $request->hasSession()) { - return; - } - - $request->setSession($this->container->get('session')); - } - - public static function getSubscribedEvents() - { - return array( - KernelEvents::REQUEST => array('onKernelRequest', 128), - ); + return $this->container->get('session'); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php b/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php index d0360c3e2c969..b32faa2f05668 100644 --- a/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php +++ b/src/Symfony/Bundle/FrameworkBundle/EventListener/TestSessionListener.php @@ -11,23 +11,15 @@ namespace Symfony\Bundle\FrameworkBundle\EventListener; -use Symfony\Component\HttpFoundation\Cookie; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\EventListener\TestSessionListener as BaseTestSessionListener; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * TestSessionListener. * - * Saves session in test environment. - * - * @author Bulat Shakirzyanov * @author Fabien Potencier */ -class TestSessionListener implements EventSubscriberInterface +class TestSessionListener extends BaseTestSessionListener { protected $container; @@ -36,50 +28,12 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - public function onKernelRequest(GetResponseEvent $event) + protected function getSession() { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { - return; - } - - // bootstrap the session if (!$this->container->has('session')) { return; } - $session = $this->container->get('session'); - $cookies = $event->getRequest()->cookies; - - if ($cookies->has($session->getName())) { - $session->setId($cookies->get($session->getName())); - } - } - - /** - * Checks if session was initialized and saves if current request is master - * Runs on 'kernel.response' in test environment - * - * @param FilterResponseEvent $event - */ - public function onKernelResponse(FilterResponseEvent $event) - { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { - return; - } - - $session = $event->getRequest()->getSession(); - if ($session && $session->isStarted()) { - $session->save(); - $params = session_get_cookie_params(); - $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); - } - } - - public static function getSubscribedEvents() - { - return array( - KernelEvents::REQUEST => array('onKernelRequest', 192), - KernelEvents::RESPONSE => array('onKernelResponse', -128), - ); + return $this->container->get('session'); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 3aaf22a623ab0..30d06fd7af952 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass; 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\TemplatingPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass; @@ -29,9 +30,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Scope; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Bundle\Bundle; -use Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass; /** * Bundle. @@ -71,6 +72,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new TemplatingPass()); $container->addCompilerPass(new AddConstraintValidatorsPass()); $container->addCompilerPass(new AddValidatorInitializersPass()); + $container->addCompilerPass(new AddConsoleCommandPass()); $container->addCompilerPass(new FormPass()); $container->addCompilerPass(new TranslatorPass()); $container->addCompilerPass(new AddCacheWarmerPass()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml index 0e07cdb5d9f63..def9aea920699 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml @@ -13,6 +13,8 @@ Symfony\Component\HttpKernel\DataCollector\TimeDataCollector Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector + Symfony\Component\Form\Extension\DataCollector\FormDataCollector + Symfony\Component\Form\Extension\DataCollector\FormDataExtractor @@ -32,6 +34,7 @@ + @@ -43,6 +46,7 @@ + @@ -53,5 +57,13 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index e7d1c3c7d47b5..c457e4f903a36 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -9,6 +9,7 @@ Symfony\Component\Stopwatch\Stopwatch %kernel.cache_dir%/%kernel.container_class%.xml Symfony\Component\HttpKernel\Controller\TraceableControllerResolver + Symfony\Component\HttpKernel\EventListener\DebugHandlersListener @@ -16,10 +17,9 @@ - + - @@ -33,5 +33,20 @@ deprecation + + + + + scream + + + + + + + + terminateWithException + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml index c0202e1516a7c..3038f40e97b46 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml @@ -7,7 +7,6 @@ Symfony\Component\HttpKernel\HttpCache\Esi Symfony\Component\HttpKernel\EventListener\EsiListener - Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer @@ -17,13 +16,5 @@ - - - - - - - %fragment.path% - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index bf63332ced1e6..23d8ec260cafb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -10,7 +10,7 @@ Symfony\Component\Form\FormFactory Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser - Symfony\Component\PropertyAccess\PropertyAccessor + Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler @@ -54,9 +54,6 @@ - - - @@ -152,9 +149,13 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 57cad204aa386..4903004f32bac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -4,14 +4,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider - - - - - %kernel.secret% + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml new file mode 100644 index 0000000000000..5d4faac4acf71 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml @@ -0,0 +1,26 @@ + + + + + + Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy + Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml index 595db6d2741c6..a6320b7f04219 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml @@ -9,6 +9,7 @@ Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer Symfony\Bundle\FrameworkBundle\Fragment\ContainerAwareHIncludeFragmentRenderer + Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer /_fragment @@ -16,7 +17,7 @@ %kernel.debug% - + @@ -33,5 +34,13 @@ %fragment.renderer.hinclude.global_template% %fragment.path% + + + + + + + %fragment.path% + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml index a8666606e150f..cee86b3b72cfa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml @@ -29,6 +29,7 @@ %profiler_listener.only_exceptions% %profiler_listener.only_master_requests% + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml new file mode 100644 index 0000000000000..18026144e4573 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_access.xml @@ -0,0 +1,14 @@ + + + + + + Symfony\Component\PropertyAccess\PropertyAccessor + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/request.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/request.xml new file mode 100644 index 0000000000000..cc836c35e3b0f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/request.xml @@ -0,0 +1,17 @@ + + + + + + Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 9e21db4519151..6b2f7c9688699 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -94,7 +94,7 @@ - + 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 17b817c325303..4d37c96e1314a 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 @@ -7,6 +7,15 @@ + + + + + + + + + @@ -16,6 +25,7 @@ + @@ -31,9 +41,17 @@ + + + + + + + + @@ -93,6 +111,19 @@ + + + + + + + + + + + + + @@ -130,9 +161,15 @@ + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml new file mode 100644 index 0000000000000..2b6307a9ef5e1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml @@ -0,0 +1,19 @@ + + + + + + Symfony\Component\Security\Core\Util\SecureRandom + + + + + + + %kernel.cache_dir%/secure_random.seed + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml new file mode 100644 index 0000000000000..143c8a68efe83 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml @@ -0,0 +1,27 @@ + + + + + + Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator + Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage + Symfony\Component\Security\Csrf\CsrfTokenManager + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 674e28f1c98a0..4457dbb27669c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -12,6 +12,7 @@ Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer Symfony\Component\HttpKernel\Config\FileLocator Symfony\Component\HttpKernel\UriSigner + Symfony\Component\HttpFoundation\RequestStack @@ -23,8 +24,11 @@ + + + @@ -39,6 +43,8 @@ YourRequestClass to the Kernel. This service definition only defines the scope of the request. It is used to check references scope. + + This service is deprecated, you should use the request_stack service instead. --> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index a6a129ee0a73e..591f92e456ddc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -8,10 +8,13 @@ Symfony\Component\HttpFoundation\Session\Session Symfony\Component\HttpFoundation\Session\Flash\FlashBag Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag + Symfony\Component\HttpFoundation\Session\Storage\MetadataBag + _sf2_meta Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler + Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler Symfony\Bundle\FrameworkBundle\EventListener\SessionListener @@ -22,13 +25,20 @@ + + %session.storage.options% + + @@ -37,12 +47,16 @@ %kernel.cache_dir%/sessions + MOCKSESSID + %session.save_path% + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml index b06c9af82f952..59da78fc41689 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml @@ -52,11 +52,9 @@ %templating.loader.cache.path% - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml index 0c6e0dcba4dc4..49e79416e1eba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml @@ -5,15 +5,10 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Symfony\Bundle\FrameworkBundle\Templating\Debugger Symfony\Bundle\FrameworkBundle\Templating\TimedPhpEngine - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml index 3caa29fb84332..0ebb44bbd2708 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml @@ -15,6 +15,7 @@ Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper + Symfony\Bundle\FrameworkBundle\Templating\Helper\StopwatchHelper Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine Symfony\Component\Form\FormRenderer Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables @@ -66,12 +67,12 @@ - + - + @@ -101,6 +102,11 @@ + + + + + %templating.helper.form.resources% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 5e6ec40ff4cb9..300f24da7f6e7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -18,6 +18,7 @@ Symfony\Component\Translation\Loader\IcuResFileLoader Symfony\Component\Translation\Loader\IcuDatFileLoader Symfony\Component\Translation\Loader\IniFileLoader + Symfony\Component\Translation\Loader\JsonFileLoader Symfony\Component\Translation\Dumper\PhpFileDumper Symfony\Component\Translation\Dumper\XliffFileDumper Symfony\Component\Translation\Dumper\PoFileDumper @@ -26,6 +27,7 @@ Symfony\Component\Translation\Dumper\QtFileDumper Symfony\Component\Translation\Dumper\CsvFileDumper Symfony\Component\Translation\Dumper\IniFileDumper + Symfony\Component\Translation\Dumper\JsonFileDumper Symfony\Component\Translation\Dumper\IcuResFileDumper Symfony\Bundle\FrameworkBundle\Translation\PhpExtractor Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader @@ -90,6 +92,10 @@ + + + + @@ -122,6 +128,10 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index 0bc70040a2068..336379a11bd66 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -5,34 +5,33 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Symfony\Component\Validator\Validator - Symfony\Component\Validator\Mapping\ClassMetadataFactory + Symfony\Component\Validator\ValidatorInterface + Symfony\Component\Validator\ValidatorBuilderInterface + Symfony\Component\Validator\Validation Symfony\Component\Validator\Mapping\Cache\ApcCache - Symfony\Component\Validator\Mapping\Loader\LoaderChain - Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader - Symfony\Component\Validator\Mapping\Loader\AnnotationLoader - Symfony\Component\Validator\Mapping\Loader\XmlFilesLoader - Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory - - + Symfony\Component\Validator\Constraints\ExpressionValidator + Symfony\Component\Validator\Constraints\EmailValidator - - - - - %validator.translation_domain% - - + - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 177821a5afb24..6c1dd73e2e615 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -38,7 +38,7 @@ %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 63d16bd357dc6..ac1077a205ae3 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,6 +1,10 @@ -id="escape($id) ?>" -name="escape($full_name) ?>" -disabled="disabled" +id="escape($id) ?>" name="escape($full_name) ?>" disabled="disabled" $v): ?> - escape($k), $view->escape($v)) ?> - + +escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> + +escape($k), $view->escape($k)) ?> + +escape($k), $view->escape($v)) ?> + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php index da9bec42ec543..77c60d7dfb3d3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_errors.html.php @@ -1,4 +1,4 @@ - + 0): ?>
  • getMessage() ?>
  • 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 9c3af35ffb9aa..e7b23d394daec 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"> + $v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?> enctype="multipart/form-data"> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php index 2b3e5abdaef36..5d7654f54cdc6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_widget_simple.html.php @@ -1,5 +1 @@ -value="escape($value) ?>" - block($form, 'widget_attributes') ?> -/> +block($form, 'widget_attributes') ?> value="escape($value) ?>" /> 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 210b84cad5a4a..c2260477317e5 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,10 +1,12 @@ -id="escape($id) ?>" -name="escape($full_name) ?>" -readonly="readonly" +id="escape($id) ?>" name="escape($full_name) ?>" readonly="readonly" disabled="disabled" required="required" -maxlength="escape($max_length) ?>" -pattern="escape($pattern) ?>" $v): ?> - escape($k), $view->escape(in_array($v, array('placeholder', 'title')) ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> - + +escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> + +escape($k), $view->escape($k)) ?> + +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 2a8e979b7f904..327925a537196 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,2 +1,10 @@ -id="escape($id) ?>" - $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?> +id="escape($id) ?>" + $v): ?> + +escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> + +escape($k), $view->escape($k)) ?> + +escape($k), $view->escape($v)) ?> + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index 62e010d1fd081..cee502d404297 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -41,7 +41,7 @@ public function __construct(ContainerInterface $container, $resource, array $opt $this->container = $container; $this->resource = $resource; - $this->context = null === $context ? new RequestContext() : $context; + $this->context = $context ?: new RequestContext(); $this->setOptions($options); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Debugger.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Debugger.php index 19a59381d0c56..ff2d5edc4b3a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Debugger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Debugger.php @@ -18,6 +18,8 @@ * Binds the Symfony templating loader debugger to the Symfony logger. * * @author Fabien Potencier + * + * @deprecated Deprecated in 2.4, to be removed in 3.0. Use Psr\Log\LoggerInterface instead. */ class Debugger implements DebuggerInterface { diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php b/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php index 0493d43d29499..c204ffa2f03b2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php @@ -39,50 +39,42 @@ public function __construct(ContainerInterface $container, array $engineIds) /** * {@inheritdoc} */ - public function supports($name) + public function getEngine($name) { - foreach ($this->engines as $i => $engine) { - if (is_string($engine)) { - $engine = $this->engines[$i] = $this->container->get($engine); - } - - if ($engine->supports($name)) { - return true; - } - } + $this->resolveEngines(); - return false; + return parent::getEngine($name); } /** * {@inheritdoc} */ - protected function getEngine($name) + public function renderResponse($view, array $parameters = array(), Response $response = null) { - foreach ($this->engines as $i => $engine) { - if (is_string($engine)) { - $engine = $this->engines[$i] = $this->container->get($engine); - } + $engine = $this->getEngine($view); - if ($engine->supports($name)) { - return $engine; - } + if ($engine instanceof EngineInterface) { + return $engine->renderResponse($view, $parameters, $response); } - throw new \RuntimeException(sprintf('No engine is able to work with the template "%s".', $name)); + if (null === $response) { + $response = new Response(); + } + + $response->setContent($engine->render($view, $parameters)); + + return $response; } /** - * Renders a view and returns a Response. - * - * @param string $view The view name - * @param array $parameters An array of parameters to pass to the view - * @param Response $response A Response instance - * - * @return Response A Response instance + * Resolved engine ids to their real engine instances from the container. */ - public function renderResponse($view, array $parameters = array(), Response $response = null) + private function resolveEngines() { - return $this->getEngine($view)->renderResponse($view, $parameters, $response); + foreach ($this->engines as $i => $engine) { + if (is_string($engine)) { + $this->engines[$i] = $this->container->get($engine); + } + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/EngineInterface.php b/src/Symfony/Bundle/FrameworkBundle/Templating/EngineInterface.php index edc087e867da8..dcf58975176bb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/EngineInterface.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/EngineInterface.php @@ -29,6 +29,8 @@ interface EngineInterface extends BaseEngineInterface * @param Response $response A Response instance * * @return Response A Response instance + * + * @throws \RuntimeException if the template cannot be rendered */ public function renderResponse($view, array $parameters = array(), Response $response = null); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php b/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php index 97feb1403b055..df0971588692d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/GlobalVariables.php @@ -78,8 +78,8 @@ public function getUser() */ public function getRequest() { - if ($this->container->has('request') && $request = $this->container->get('request')) { - return $request; + if ($this->container->has('request_stack')) { + return $this->container->get('request_stack')->getCurrentRequest(); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php index f04bb5e62853a..756476f5663f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php @@ -121,7 +121,7 @@ public function fileExcerpt($file, $line) if (extension_loaded('fileinfo')) { $finfo = new \Finfo(); - // Check if the file is an application/octet-stream (eg. Phar file) because hightlight_file cannot parse these files + // Check if the file is an application/octet-stream (eg. Phar file) because highlight_file cannot parse these files if ('application/octet-stream' === $finfo->file($file, FILEINFO_MIME_TYPE)) { return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php index 060a6ce6bf6e5..7bfa1e72bfc53 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @@ -247,7 +247,7 @@ public function block(FormView $view, $blockName, array $variables = array()) * Check the token in your action using the same intention. * * - * $csrfProvider = $this->get('form.csrf_provider'); + * $csrfProvider = $this->get('security.csrf.token_generator'); * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { * throw new \RuntimeException('CSRF attack detected.'); * } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RequestHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RequestHelper.php index 4533bf172bc6c..d071a586c02e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RequestHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/RequestHelper.php @@ -13,6 +13,7 @@ use Symfony\Component\Templating\Helper\Helper; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; /** * RequestHelper provides access to the current request parameters. @@ -22,15 +23,24 @@ class RequestHelper extends Helper { protected $request; + protected $requestStack; /** * Constructor. * - * @param Request $request A Request instance + * @param Request|RequestStack $requestStack A RequestStack instance or a Request instance + * + * @deprecated since 2.5, passing a Request instance is deprecated and support for it will be removed in 3.0 */ - public function __construct(Request $request) + public function __construct($requestStack) { - $this->request = $request; + if ($requestStack instanceof Request) { + $this->request = $requestStack; + } elseif ($requestStack instanceof RequestStack) { + $this->requestStack = $requestStack; + } else { + throw new \InvalidArgumentException('RequestHelper only accepts a Request or a RequestStack instance.'); + } } /** @@ -45,7 +55,7 @@ public function __construct(Request $request) */ public function getParameter($key, $default = null) { - return $this->request->get($key, $default); + return $this->getRequest()->get($key, $default); } /** @@ -55,7 +65,20 @@ public function getParameter($key, $default = null) */ public function getLocale() { - return $this->request->getLocale(); + return $this->getRequest()->getLocale(); + } + + private function getRequest() + { + if ($this->requestStack) { + if (!$this->requestStack->getCurrentRequest()) { + throw new \LogicException('A Request must be available.'); + } + + return $this->requestStack->getCurrentRequest(); + } + + return $this->request; } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php index aac3f6dd7b1ee..691dde3d21c19 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/SessionHelper.php @@ -13,6 +13,7 @@ use Symfony\Component\Templating\Helper\Helper; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; /** * SessionHelper provides read-only access to the session attributes. @@ -22,15 +23,24 @@ class SessionHelper extends Helper { protected $session; + protected $requestStack; /** * Constructor. * - * @param Request $request A Request instance + * @param Request|RequestStack $requestStack A RequestStack instance or a Request instance + * + * @deprecated since 2.5, passing a Request instance is deprecated and support for it will be removed in 3.0 */ - public function __construct(Request $request) + public function __construct($requestStack) { - $this->session = $request->getSession(); + if ($requestStack instanceof Request) { + $this->session = $requestStack->getSession(); + } elseif ($requestStack instanceof RequestStack) { + $this->requestStack = $requestStack; + } else { + throw new \InvalidArgumentException('RequestHelper only accepts a Request or a RequestStack instance.'); + } } /** @@ -43,22 +53,35 @@ public function __construct(Request $request) */ public function get($name, $default = null) { - return $this->session->get($name, $default); + return $this->getSession()->get($name, $default); } public function getFlash($name, array $default = array()) { - return $this->session->getFlashBag()->get($name, $default); + return $this->getSession()->getFlashBag()->get($name, $default); } public function getFlashes() { - return $this->session->getFlashBag()->all(); + return $this->getSession()->getFlashBag()->all(); } public function hasFlash($name) { - return $this->session->getFlashBag()->has($name); + return $this->getSession()->getFlashBag()->has($name); + } + + private function getSession() + { + if (null === $this->session) { + if (!$this->requestStack->getMasterRequest()) { + throw new \LogicException('A Request must be available.'); + } + + $this->session = $this->requestStack->getMasterRequest()->getSession(); + } + + return $this->session; } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/StopwatchHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/StopwatchHelper.php new file mode 100644 index 0000000000000..47a9cf80ebcb4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/StopwatchHelper.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Templating\Helper\Helper; + +/** + * StopwatchHelper provides methods time your PHP templates. + * + * @author Wouter J + */ +class StopwatchHelper extends Helper +{ + private $stopwatch; + + public function __construct(Stopwatch $stopwatch = null) + { + $this->stopwatch = $stopwatch; + } + + public function getName() + { + return 'stopwatch'; + } + + public function __call($method, $arguments = array()) + { + if (null !== $this->stopwatch) { + if (method_exists($this->stopwatch, $method)) { + return call_user_func_array(array($this->stopwatch, $method), $arguments); + } + + throw new \BadMethodCallException(sprintf('Method "%s" of Stopwatch does not exist', $method)); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/FilesystemLoader.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/FilesystemLoader.php index 98c998deba98a..4f6cffc103b8c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/FilesystemLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/FilesystemLoader.php @@ -36,11 +36,7 @@ public function __construct(FileLocatorInterface $locator) } /** - * Loads a template. - * - * @param TemplateReferenceInterface $template A template - * - * @return FileStorage|bool false if the template cannot be loaded, a Storage instance otherwise + * {@inheritdoc} */ public function load(TemplateReferenceInterface $template) { @@ -54,12 +50,7 @@ public function load(TemplateReferenceInterface $template) } /** - * Returns true if the template is still fresh. - * - * @param TemplateReferenceInterface $template The template name as an array - * @param int $time The last modification time of the cached template (timestamp) - * - * @return bool + * {@inheritdoc} */ public function isFresh(TemplateReferenceInterface $template, $time) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/TemplateLocator.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/TemplateLocator.php index 1dc9ce56e27b5..286b7c62e4d2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/TemplateLocator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Loader/TemplateLocator.php @@ -66,7 +66,7 @@ protected function getCacheKey($template) public function locate($template, $currentPath = null, $first = true) { if (!$template instanceof TemplateReferenceInterface) { - throw new \InvalidArgumentException("The template must be an instance of TemplateReferenceInterface."); + throw new \InvalidArgumentException('The template must be an instance of TemplateReferenceInterface.'); } $key = $this->getCacheKey($template); diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/PhpEngine.php b/src/Symfony/Bundle/FrameworkBundle/Templating/PhpEngine.php index a93098eb842c3..41382f769c65f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/PhpEngine.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/PhpEngine.php @@ -46,7 +46,7 @@ public function __construct(TemplateNameParserInterface $parser, ContainerInterf } /** - * @throws \InvalidArgumentException When the helper is not defined + * {@inheritdoc} */ public function get($name) { @@ -71,13 +71,7 @@ public function setHelpers(array $helpers) } /** - * Renders a view and returns a Response. - * - * @param string $view The view name - * @param array $parameters An array of parameters to pass to the view - * @param Response $response A Response instance - * - * @return Response A Response instance + * {@inheritdoc} */ public function renderResponse($view, array $parameters = array(), Response $response = null) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateFilenameParser.php b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateFilenameParser.php index befb3e8f85771..a4a5e92cf3369 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateFilenameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateFilenameParser.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Templating; use Symfony\Component\Templating\TemplateNameParserInterface; +use Symfony\Component\Templating\TemplateReferenceInterface; /** * TemplateFilenameParser converts template filenames to @@ -24,9 +25,13 @@ class TemplateFilenameParser implements TemplateNameParserInterface /** * {@inheritdoc} */ - public function parse($file) + public function parse($name) { - $parts = explode('/', strtr($file, '\\', '/')); + if ($name instanceof TemplateReferenceInterface) { + return $name; + } + + $parts = explode('/', strtr($name, '\\', '/')); $elements = explode('.', array_pop($parts)); if (3 > count($elements)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php index 4777fbeadbb49..5730807facd4b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php @@ -25,7 +25,7 @@ class TemplateNameParser extends BaseTemplateNameParser { protected $kernel; - protected $cache; + protected $cache = array(); /** * Constructor. @@ -35,7 +35,6 @@ class TemplateNameParser extends BaseTemplateNameParser public function __construct(KernelInterface $kernel) { $this->kernel = $kernel; - $this->cache = array(); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php new file mode 100644 index 0000000000000..90255f85fa4a3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * KernelTestCase is the base class for tests needing a Kernel. + * + * @author Fabien Potencier + */ +abstract class KernelTestCase extends \PHPUnit_Framework_TestCase +{ + protected static $class; + + /** + * @var KernelInterface + */ + protected static $kernel; + + /** + * Finds the directory where the phpunit.xml(.dist) is stored. + * + * If you run tests with the PHPUnit CLI tool, everything will work as expected. + * If not, override this method in your test classes. + * + * @return string The directory where phpunit.xml(.dist) is stored + * + * @throws \RuntimeException + */ + protected static function getPhpUnitXmlDir() + { + if (!isset($_SERVER['argv']) || false === strpos($_SERVER['argv'][0], 'phpunit')) { + throw new \RuntimeException('You must override the KernelTestCase::createKernel() method.'); + } + + $dir = static::getPhpUnitCliConfigArgument(); + if (null === $dir && + (is_file(getcwd().DIRECTORY_SEPARATOR.'phpunit.xml') || + is_file(getcwd().DIRECTORY_SEPARATOR.'phpunit.xml.dist'))) { + $dir = getcwd(); + } + + // Can't continue + if (null === $dir) { + throw new \RuntimeException('Unable to guess the Kernel directory.'); + } + + if (!is_dir($dir)) { + $dir = dirname($dir); + } + + return $dir; + } + + /** + * Finds the value of the CLI configuration option. + * + * PHPUnit will use the last configuration argument on the command line, so this only returns + * the last configuration argument. + * + * @return string The value of the PHPUnit CLI configuration option + */ + private static function getPhpUnitCliConfigArgument() + { + $dir = null; + $reversedArgs = array_reverse($_SERVER['argv']); + foreach ($reversedArgs as $argIndex => $testArg) { + if (preg_match('/^-[^ \-]*c$/', $testArg) || $testArg === '--configuration') { + $dir = realpath($reversedArgs[$argIndex - 1]); + break; + } elseif (strpos($testArg, '--configuration=') === 0) { + $argPath = substr($testArg, strlen('--configuration=')); + $dir = realpath($argPath); + break; + } + } + + return $dir; + } + + /** + * Attempts to guess the kernel location. + * + * When the Kernel is located, the file is required. + * + * @return string The Kernel class name + * + * @throws \RuntimeException + */ + protected static function getKernelClass() + { + if (isset($_SERVER['KERNEL_DIR'])) { + $dir = $_SERVER['KERNEL_DIR']; + + if (!is_dir($dir)) { + $phpUnitDir = static::getPhpUnitXmlDir(); + if (is_dir("$phpUnitDir/$dir")) { + $dir = "$phpUnitDir/$dir"; + } + } + } else { + $dir = static::getPhpUnitXmlDir(); + } + + $finder = new Finder(); + $finder->name('*Kernel.php')->depth(0)->in($dir); + $results = iterator_to_array($finder); + if (!count($results)) { + throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.'); + } + + $file = current($results); + $class = $file->getBasename('.php'); + + require_once $file; + + return $class; + } + + /** + * Boots the Kernel for this test. + * + * @param array $options + */ + protected static function bootKernel(array $options = array()) + { + static::ensureKernelShutdown(); + + static::$kernel = static::createKernel($options); + static::$kernel->boot(); + } + + /** + * Creates a Kernel. + * + * Available options: + * + * * environment + * * debug + * + * @param array $options An array of options + * + * @return KernelInterface A KernelInterface instance + */ + protected static function createKernel(array $options = array()) + { + if (null === static::$class) { + static::$class = static::getKernelClass(); + } + + return new static::$class( + isset($options['environment']) ? $options['environment'] : 'test', + isset($options['debug']) ? $options['debug'] : true + ); + } + + /** + * Shuts the kernel down if it was used in the test. + */ + protected static function ensureKernelShutdown() + { + if (null !== static::$kernel) { + static::$kernel->shutdown(); + } + } + + /** + * Clean up Kernel usage in this test. + */ + protected function tearDown() + { + static::ensureKernelShutdown(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php index e282711a5b4c9..5708ef716df7b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php @@ -12,23 +12,14 @@ namespace Symfony\Bundle\FrameworkBundle\Test; use Symfony\Bundle\FrameworkBundle\Client; -use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\KernelInterface; /** * WebTestCase is the base class for functional tests. * * @author Fabien Potencier */ -abstract class WebTestCase extends \PHPUnit_Framework_TestCase +abstract class WebTestCase extends KernelTestCase { - protected static $class; - - /** - * @var KernelInterface - */ - protected static $kernel; - /** * Creates a Client. * @@ -39,139 +30,11 @@ abstract class WebTestCase extends \PHPUnit_Framework_TestCase */ protected static function createClient(array $options = array(), array $server = array()) { - if (null !== static::$kernel) { - static::$kernel->shutdown(); - } - - static::$kernel = static::createKernel($options); - static::$kernel->boot(); + static::bootKernel($options); $client = static::$kernel->getContainer()->get('test.client'); $client->setServerParameters($server); return $client; } - - /** - * Finds the directory where the phpunit.xml(.dist) is stored. - * - * If you run tests with the PHPUnit CLI tool, everything will work as expected. - * If not, override this method in your test classes. - * - * @return string The directory where phpunit.xml(.dist) is stored - * - * @throws \RuntimeException - */ - protected static function getPhpUnitXmlDir() - { - if (!isset($_SERVER['argv']) || false === strpos($_SERVER['argv'][0], 'phpunit')) { - throw new \RuntimeException('You must override the WebTestCase::createKernel() method.'); - } - - $dir = static::getPhpUnitCliConfigArgument(); - if (null === $dir && - (is_file(getcwd().DIRECTORY_SEPARATOR.'phpunit.xml') || - is_file(getcwd().DIRECTORY_SEPARATOR.'phpunit.xml.dist'))) { - $dir = getcwd(); - } - - // Can't continue - if (null === $dir) { - throw new \RuntimeException('Unable to guess the Kernel directory.'); - } - - if (!is_dir($dir)) { - $dir = dirname($dir); - } - - return $dir; - } - - /** - * Finds the value of the CLI configuration option. - * - * PHPUnit will use the last configuration argument on the command line, so this only returns - * the last configuration argument. - * - * @return string The value of the PHPUnit CLI configuration option - */ - private static function getPhpUnitCliConfigArgument() - { - $dir = null; - $reversedArgs = array_reverse($_SERVER['argv']); - foreach ($reversedArgs as $argIndex => $testArg) { - if (preg_match('/^-[^ \-]*c$/', $testArg) || $testArg === '--configuration') { - $dir = realpath($reversedArgs[$argIndex - 1]); - break; - } elseif (strpos($testArg, '--configuration=') === 0) { - $argPath = substr($testArg, strlen('--configuration=')); - $dir = realpath($argPath); - break; - } - } - - return $dir; - } - - /** - * Attempts to guess the kernel location. - * - * When the Kernel is located, the file is required. - * - * @return string The Kernel class name - * - * @throws \RuntimeException - */ - protected static function getKernelClass() - { - $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir(); - - $finder = new Finder(); - $finder->name('*Kernel.php')->depth(0)->in($dir); - $results = iterator_to_array($finder); - if (!count($results)) { - throw new \RuntimeException('Either set KERNEL_DIR in your phpunit.xml according to http://symfony.com/doc/current/book/testing.html#your-first-functional-test or override the WebTestCase::createKernel() method.'); - } - - $file = current($results); - $class = $file->getBasename('.php'); - - require_once $file; - - return $class; - } - - /** - * Creates a Kernel. - * - * Available options: - * - * * environment - * * debug - * - * @param array $options An array of options - * - * @return KernelInterface A KernelInterface instance - */ - protected static function createKernel(array $options = array()) - { - if (null === static::$class) { - static::$class = static::getKernelClass(); - } - - return new static::$class( - isset($options['environment']) ? $options['environment'] : 'test', - isset($options['debug']) ? $options['debug'] : true - ); - } - - /** - * Shuts the kernel down if it was used in the test. - */ - protected function tearDown() - { - if (null !== static::$kernel) { - static::$kernel->shutdown(); - } - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php index 42fcb6f597dd7..612d6325dd26d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php @@ -74,6 +74,18 @@ private function getKernel(array $bundles) ->with($this->equalTo('event_dispatcher')) ->will($this->returnValue($dispatcher)) ; + $container + ->expects($this->once()) + ->method('hasParameter') + ->with($this->equalTo('console.command.ids')) + ->will($this->returnValue(true)) + ; + $container + ->expects($this->once()) + ->method('getParameter') + ->with($this->equalTo('console.command.ids')) + ->will($this->returnValue(array())) + ; $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); $kernel diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php new file mode 100644 index 0000000000000..ad1a60e1c87d4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; + +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getDescribeRouteCollectionTestData */ + public function testDescribeRouteCollection(RouteCollection $routes, $expectedDescription) + { + $this->assertDescription($expectedDescription, $routes); + } + + public function getDescribeRouteCollectionTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getRouteCollections()); + } + + /** @dataProvider getDescribeRouteTestData */ + public function testDescribeRoute(Route $route, $expectedDescription) + { + $this->assertDescription($expectedDescription, $route); + } + + public function getDescribeRouteTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getRoutes()); + } + + /** @dataProvider getDescribeContainerParametersTestData */ + public function testDescribeContainerParameters(ParameterBag $parameters, $expectedDescription) + { + $this->assertDescription($expectedDescription, $parameters); + } + + public function getDescribeContainerParametersTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getContainerParameters()); + } + + /** @dataProvider getDescribeContainerBuilderTestData */ + public function testDescribeContainerBuilder(ContainerBuilder $builder, $expectedDescription, array $options) + { + $this->assertDescription($expectedDescription, $builder, $options); + } + + public function getDescribeContainerBuilderTestData() + { + return $this->getContainerBuilderDescriptionTestData(ObjectsProvider::getContainerBuilders()); + } + + /** @dataProvider getDescribeContainerDefinitionTestData */ + public function testDescribeContainerDefinition(Definition $definition, $expectedDescription) + { + $this->assertDescription($expectedDescription, $definition); + } + + public function getDescribeContainerDefinitionTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getContainerDefinitions()); + } + + /** @dataProvider getDescribeContainerAliasTestData */ + public function testDescribeContainerAlias(Alias $alias, $expectedDescription) + { + $this->assertDescription($expectedDescription, $alias); + } + + public function getDescribeContainerAliasTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getContainerAliases()); + } + + /** @dataProvider getDescribeContainerParameterTestData */ + public function testDescribeContainerParameter($parameter, $expectedDescription, array $options) + { + $this->assertDescription($expectedDescription, $parameter, $options); + } + + public function getDescribeContainerParameterTestData() + { + $data = $this->getDescriptionTestData(ObjectsProvider::getContainerParameter()); + + array_push($data[0], array('parameter' => 'database_name')); + + return $data; + } + + abstract protected function getDescriptor(); + abstract protected function getFormat(); + + private function assertDescription($expectedDescription, $describedObject, array $options = array()) + { + $options['raw_output'] = true; + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $this->getDescriptor()->describe($output, $describedObject, $options); + + if ('json' === $this->getFormat()) { + $this->assertEquals(json_decode($expectedDescription), json_decode($output->fetch())); + } else { + $this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $output->fetch()))); + } + } + + private function getDescriptionTestData(array $objects) + { + $data = array(); + foreach ($objects as $name => $object) { + $description = file_get_contents(sprintf('%s/../../Fixtures/Descriptor/%s.%s', __DIR__, $name, $this->getFormat())); + $data[] = array($object, $description); + } + + return $data; + } + + private function getContainerBuilderDescriptionTestData(array $objects) + { + $variations = array( + 'services' => array('show_private' => true), + 'public' => array('show_private' => false), + 'tag1' => array('show_private' => true, 'tag' => 'tag1'), + 'tags' => array('group_by' => 'tags', 'show_private' => true), + ); + + $data = array(); + foreach ($objects as $name => $object) { + foreach ($variations as $suffix => $options) { + $description = file_get_contents(sprintf('%s/../../Fixtures/Descriptor/%s_%s.%s', __DIR__, $name, $suffix, $this->getFormat())); + $data[] = array($object, $description, $options); + } + } + + return $data; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/JsonDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/JsonDescriptorTest.php new file mode 100644 index 0000000000000..7f7a0ae15cfd7 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/JsonDescriptorTest.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\Console\Descriptor; + +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\JsonDescriptor; + +class JsonDescriptorTest extends AbstractDescriptorTest +{ + protected function setUp() + { + if (PHP_VERSION_ID < 50400) { + $this->markTestSkipped('Test skipped on PHP 5.3 as JSON_PRETTY_PRINT does not exist.'); + } + } + + protected function getDescriptor() + { + return new JsonDescriptor(); + } + + protected function getFormat() + { + return 'json'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/MarkdownDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/MarkdownDescriptorTest.php new file mode 100644 index 0000000000000..fbb5aaa962689 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/MarkdownDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; + +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\MarkdownDescriptor; + +class MarkdownDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new MarkdownDescriptor(); + } + + protected function getFormat() + { + return 'md'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php new file mode 100644 index 0000000000000..ca791b33274ab --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ObjectsProvider +{ + public static function getRouteCollections() + { + $collection1 = new RouteCollection(); + foreach (self::getRoutes() as $name => $route) { + $collection1->add($name, $route); + } + + return array('route_collection_1' => $collection1); + } + + public static function getRoutes() + { + return array( + 'route_1' => new Route( + '/hello/{name}', + array('name' => 'Joseph'), + array('name' => '[a-z]+'), + array('opt1' => 'val1', 'opt2' => 'val2'), + 'localhost', + array('http', 'https'), + array('get', 'head') + ), + 'route_2' => new Route( + '/name/add', + array(), + array(), + array('opt1' => 'val1', 'opt2' => 'val2'), + 'localhost', + array('http', 'https'), + array('put', 'post') + ), + ); + } + + public static function getContainerParameters() + { + return array( + 'parameters_1' => new ParameterBag(array( + 'integer' => 12, + 'string' => 'Hello world!', + 'boolean' => true, + 'array' => array(12, 'Hello world!', true), + )), + ); + } + + public static function getContainerParameter() + { + $builder = new ContainerBuilder(); + $builder->setParameter('database_name', 'symfony'); + + return array( + 'parameter' => $builder, + ); + } + + public static function getContainerBuilders() + { + $builder1 = new ContainerBuilder(); + $builder1->setDefinitions(self::getContainerDefinitions()); + $builder1->setAliases(self::getContainerAliases()); + + return array('builder_1' => $builder1); + } + + public static function getContainerDefinitions() + { + $definition1 = new Definition('Full\\Qualified\\Class1'); + $definition2 = new Definition('Full\\Qualified\\Class2'); + + return array( + 'definition_1' => $definition1 + ->setPublic(true) + ->setSynthetic(false) + ->setLazy(true) + ->setSynchronized(true) + ->setAbstract(true) + ->setFactoryClass('Full\\Qualified\\FactoryClass') + ->setFactoryMethod('get'), + 'definition_2' => $definition2 + ->setPublic(false) + ->setSynthetic(true) + ->setFile('/path/to/file') + ->setLazy(false) + ->setSynchronized(false) + ->setAbstract(false) + ->addTag('tag1', array('attr1' => 'val1', 'attr2' => 'val2')) + ->addTag('tag1', array('attr3' => 'val3')) + ->addTag('tag2') + ->setFactoryService('factory.service') + ->setFactoryMethod('get'), + ); + } + + public static function getContainerAliases() + { + return array( + 'alias_1' => new Alias('service_1', true), + 'alias_2' => new Alias('service_2', false), + ); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php new file mode 100644 index 0000000000000..ce4f377c508fd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/TextDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; + +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor; + +class TextDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new TextDescriptor(); + } + + protected function getFormat() + { + return 'txt'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/XmlDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/XmlDescriptorTest.php new file mode 100644 index 0000000000000..8cb9a71697f6b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/XmlDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor; + +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor; + +class XmlDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new XmlDescriptor(); + } + + protected function getFormat() + { + return 'xml'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php index 9e120160e5102..6ff09c348ceb0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTest.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; class ControllerTest extends TestCase @@ -24,13 +25,16 @@ public function testForward() $request->setLocale('fr'); $request->setRequestFormat('xml'); + $requestStack = new RequestStack(); + $requestStack->push($request); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); $kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) { return new Response($request->getRequestFormat().'--'.$request->getLocale()); })); $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); - $container->expects($this->at(0))->method('get')->will($this->returnValue($request)); + $container->expects($this->at(0))->method('get')->will($this->returnValue($requestStack)); $container->expects($this->at(1))->method('get')->will($this->returnValue($kernel)); $controller = new Controller(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php index a892c711a3208..64d9a8b16a1f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -19,7 +19,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\TestCase; /** - * @author Marcin Sikon + * @author Marcin Sikon */ class RedirectControllerTest extends TestCase { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConsoleCommandPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConsoleCommandPassTest.php new file mode 100644 index 0000000000000..52f9b0e394d90 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConsoleCommandPassTest.php @@ -0,0 +1,119 @@ + + * + * 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 Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class AddConsoleCommandPassTest extends \PHPUnit_Framework_TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + $container->setParameter('my-command.class', 'Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\MyCommand'); + + $definition = new Definition('%my-command.class%'); + $definition->addTag('console.command'); + $container->setDefinition('my-command', $definition); + + $container->compile(); + + $alias = 'console.command.symfony_bundle_frameworkbundle_tests_dependencyinjection_compiler_mycommand'; + $this->assertTrue($container->hasAlias($alias)); + $this->assertSame('my-command', (string) $container->getAlias($alias)); + + $this->assertTrue($container->hasParameter('console.command.ids')); + $this->assertSame(array('my-command'), $container->getParameter('console.command.ids')); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The service "my-command" tagged "console.command" must be public. + */ + public function testProcessThrowAnExceptionIfTheServiceIsNotPublic() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + + $definition = new Definition('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\MyCommand'); + $definition->addTag('console.command'); + $definition->setPublic(false); + $container->setDefinition('my-command', $definition); + + $container->compile(); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The service "my-command" tagged "console.command" must not be abstract. + */ + public function testProcessThrowAnExceptionIfTheServiceIsAbstract() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + + $definition = new Definition('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\MyCommand'); + $definition->addTag('console.command'); + $definition->setAbstract(true); + $container->setDefinition('my-command', $definition); + + $container->compile(); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The service "my-command" tagged "console.command" must be a subclass of "Symfony\Component\Console\Command\Command". + */ + public function testProcessThrowAnExceptionIfTheServiceIsNotASubclassOfCommand() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + + $definition = new Definition('SplObjectStorage'); + $definition->addTag('console.command'); + $container->setDefinition('my-command', $definition); + + $container->compile(); + } + + public function testHttpKernelRegisterCommandsIngoreCommandAsAService() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddConsoleCommandPass()); + $definition = new Definition('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\MyCommand'); + $definition->addTag('console.command'); + $container->setDefinition('my-command', $definition); + $container->compile(); + + $application = $this->getMock('Symfony\Component\Console\Application'); + // Never called, because it's the + // Symfony\Bundle\FrameworkBundle\Console\Application that register + // commands as a service + $application->expects($this->never())->method('add'); + + $bundle = new ExtensionPresentBundle(); + $bundle->setContainer($container); + $bundle->registerCommands($application); + } +} + +class MyCommand extends Command +{ +} + +class ExtensionPresentBundle extends Bundle +{ +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 1bcb01189ef13..8e40ee11fe10e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -93,9 +93,15 @@ protected static function getBundleDefaultConfig() 'trusted_proxies' => array(), 'ide' => null, 'default_locale' => 'en', - 'form' => array('enabled' => false), + 'form' => array( + 'enabled' => false, + 'csrf_protection' => array( + 'enabled' => null, // defaults to csrf_protection.enabled + 'field_name' => null, + ), + ), 'csrf_protection' => array( - 'enabled' => true, + 'enabled' => false, 'field_name' => '_token', ), 'esi' => array('enabled' => false), @@ -120,7 +126,10 @@ protected static function getBundleDefaultConfig() 'validation' => array( 'enabled' => false, 'enable_annotations' => false, + 'static_method' => array('loadValidatorMetadata'), 'translation_domain' => 'validators', + 'strict_email' => false, + 'api' => PHP_VERSION_ID < 50309 ? '2.4' : '2.5-bc', ), 'annotations' => array( 'cache' => 'file', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf.php new file mode 100644 index 0000000000000..ff5286c7a2c62 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf.php @@ -0,0 +1,16 @@ +loadFromExtension('framework', array( + 'csrf_protection' => array( + 'enabled' => false, + ), + 'form' => array( + 'enabled' => true, + 'csrf_protection' => array( + 'enabled' => true, + ), + ), + 'session' => array( + 'handler_id' => null, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf_disabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf_disabled.php new file mode 100644 index 0000000000000..b7bbd8bf3c886 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf_disabled.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'csrf_protection' => array( + 'enabled' => false, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf_needs_session.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf_needs_session.php new file mode 100644 index 0000000000000..d1df1c596233c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf_needs_session.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'csrf_protection' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_csrf_sets_field_name.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_csrf_sets_field_name.php new file mode 100644 index 0000000000000..3022ffc986656 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_csrf_sets_field_name.php @@ -0,0 +1,14 @@ +loadFromExtension('framework', array( + 'csrf_protection' => array( + 'enabled' => true, + 'field_name' => '_custom', + ), + 'form' => array( + 'enabled' => true, + ), + 'session' => array( + 'handler_id' => null, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_csrf_under_form_sets_field_name.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_csrf_under_form_sets_field_name.php new file mode 100644 index 0000000000000..e3d13b4f9223d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_csrf_under_form_sets_field_name.php @@ -0,0 +1,17 @@ +loadFromExtension('framework', array( + 'csrf_protection' => array( + 'enabled' => true, + 'field_name' => '_custom', + ), + 'form' => array( + 'enabled' => true, + 'csrf_protection' => array( + 'field_name' => '_custom_form', + ), + ), + 'session' => array( + 'handler_id' => null, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_no_csrf.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_no_csrf.php new file mode 100644 index 0000000000000..db7a6b93dcaae --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/form_no_csrf.php @@ -0,0 +1,8 @@ +loadFromExtension('framework', array( + 'form' => array( + 'enabled' => true, + 'csrf_protection' => false, + ), +)); 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 95f94e50958bd..baa17dc92e7b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -72,4 +72,13 @@ 'file_cache_dir' => '%kernel.cache_dir%/annotations', ), 'ide' => 'file%%link%%format', + 'request' => array( + 'formats' => array( + 'csv' => array( + 'text/csv', + 'text/plain', + ), + 'pdf' => 'application/pdf', + ), + ), )); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/request.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/request.php new file mode 100644 index 0000000000000..1e7cb2921c4cd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/request.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'request' => array( + 'formats' => array(), + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_4_api.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_4_api.php new file mode 100644 index 0000000000000..120b7eed526b6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_4_api.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', array( + 'secret' => 's3cr3t', + 'validation' => array( + 'enabled' => true, + 'api' => '2.4', + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_5_api.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_5_api.php new file mode 100644 index 0000000000000..9fa01821b8dbc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_5_api.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', array( + 'secret' => 's3cr3t', + 'validation' => array( + 'enabled' => true, + 'api' => '2.5', + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_5_bc_api.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_5_bc_api.php new file mode 100644 index 0000000000000..e975ee3cdd0fd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_5_bc_api.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', array( + 'secret' => 's3cr3t', + 'validation' => array( + 'enabled' => true, + 'api' => '2.5-bc', + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_auto_api.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_auto_api.php new file mode 100644 index 0000000000000..4133928ff44c5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_auto_api.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', array( + 'secret' => 's3cr3t', + 'validation' => array( + 'enabled' => true, + 'api' => 'auto', + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_implicit_api.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_implicit_api.php new file mode 100644 index 0000000000000..9eae9998cc93b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_implicit_api.php @@ -0,0 +1,8 @@ +loadFromExtension('framework', array( + 'secret' => 's3cr3t', + 'validation' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_multiple_static_methods.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_multiple_static_methods.php new file mode 100644 index 0000000000000..476da7948e166 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_multiple_static_methods.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', array( + 'secret' => 's3cr3t', + 'validation' => array( + 'enabled' => true, + 'static_method' => array('loadFoo', 'loadBar'), + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_no_static_method.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_no_static_method.php new file mode 100644 index 0000000000000..b428e06f5c3c0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_no_static_method.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', array( + 'secret' => 's3cr3t', + 'validation' => array( + 'enabled' => true, + 'static_method' => false, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml new file mode 100644 index 0000000000000..4484c353ede26 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf_disabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf_disabled.xml new file mode 100644 index 0000000000000..635557f938acb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf_disabled.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf_needs_session.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf_needs_session.xml new file mode 100644 index 0000000000000..0a08d2351aaf2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf_needs_session.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml new file mode 100644 index 0000000000000..8e1803a6c7217 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_sets_field_name.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml new file mode 100644 index 0000000000000..d08ac9e542784 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_csrf_under_form_sets_field_name.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_no_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_no_csrf.xml new file mode 100644 index 0000000000000..fdeb60acf9934 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/form_no_csrf.xml @@ -0,0 +1,14 @@ + + + + + + + + + + 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 bfdaabc71f31f..16aef6f09f624 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -13,6 +13,15 @@ + + + text/csv + text/plain + + + application/pdf + + loader.foo loader.bar diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/request.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/request.xml new file mode 100644 index 0000000000000..320d1b187525a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/request.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_4_api.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_4_api.xml new file mode 100644 index 0000000000000..247c66d8dfd08 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_4_api.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_5_api.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_5_api.xml new file mode 100644 index 0000000000000..213d281f4b1ab --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_5_api.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_5_bc_api.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_5_bc_api.xml new file mode 100644 index 0000000000000..38eff2fddd063 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_5_bc_api.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_auto_api.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_auto_api.xml new file mode 100644 index 0000000000000..2be409bfb6660 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_auto_api.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_implicit_api.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_implicit_api.xml new file mode 100644 index 0000000000000..a41c8f23c9c60 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_implicit_api.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_multiple_static_methods.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_multiple_static_methods.xml new file mode 100644 index 0000000000000..053f574bafe9b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_multiple_static_methods.xml @@ -0,0 +1,15 @@ + + + + + + + loadFoo + loadBar + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_no_static_method.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_no_static_method.xml new file mode 100644 index 0000000000000..d26c7a2be50ef --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_no_static_method.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml index ce5fc591edbfe..c08db90f5b7ae 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf.yml @@ -1,6 +1,8 @@ framework: + csrf_protection: false secret: s3cr3t - form: ~ + form: + csrf_protection: true session: ~ - # CSRF should be enabled by default + # CSRF is disabled by default # csrf_protection: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf_disabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf_disabled.yml new file mode 100644 index 0000000000000..b094cc481fea8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf_disabled.yml @@ -0,0 +1,2 @@ +framework: + csrf_protection: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf_needs_session.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf_needs_session.yml new file mode 100644 index 0000000000000..b8065b6fb678b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/csrf_needs_session.yml @@ -0,0 +1,2 @@ +framework: + csrf_protection: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_csrf_sets_field_name.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_csrf_sets_field_name.yml new file mode 100644 index 0000000000000..3347cbfaf1bf5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_csrf_sets_field_name.yml @@ -0,0 +1,5 @@ +framework: + csrf_protection: + field_name: _custom + form: ~ + session: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_csrf_under_form_sets_field_name.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_csrf_under_form_sets_field_name.yml new file mode 100644 index 0000000000000..a4bb34259f58a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_csrf_under_form_sets_field_name.yml @@ -0,0 +1,7 @@ +framework: + csrf_protection: + field_name: _custom + form: + csrf_protection: + field_name: _custom_form + session: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_no_csrf.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_no_csrf.yml new file mode 100644 index 0000000000000..e3ac7e8daf42d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/form_no_csrf.yml @@ -0,0 +1,4 @@ +framework: + form: + csrf_protection: + enabled: false 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 0a159ddc34c2d..b4adc25782d45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -56,3 +56,7 @@ framework: debug: true file_cache_dir: %kernel.cache_dir%/annotations ide: file%%link%%format + request: + formats: + csv: ['text/csv', 'text/plain'] + pdf: 'application/pdf' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/request.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/request.yml new file mode 100644 index 0000000000000..9beae1dc59755 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/request.yml @@ -0,0 +1,3 @@ +framework: + request: + formats: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_4_api.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_4_api.yml new file mode 100644 index 0000000000000..cce573fb67e3c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_4_api.yml @@ -0,0 +1,5 @@ +framework: + secret: s3cr3t + validation: + enabled: true + api: 2.4 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_5_api.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_5_api.yml new file mode 100644 index 0000000000000..d1af6c4e27c6d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_5_api.yml @@ -0,0 +1,5 @@ +framework: + secret: s3cr3t + validation: + enabled: true + api: 2.5 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_5_bc_api.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_5_bc_api.yml new file mode 100644 index 0000000000000..ebf12e06f8439 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_5_bc_api.yml @@ -0,0 +1,5 @@ +framework: + secret: s3cr3t + validation: + enabled: true + api: 2.5-bc diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_auto_api.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_auto_api.yml new file mode 100644 index 0000000000000..27619e63ed7a9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_auto_api.yml @@ -0,0 +1,5 @@ +framework: + secret: s3cr3t + validation: + enabled: true + api: auto diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_implicit_api.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_implicit_api.yml new file mode 100644 index 0000000000000..9b3aa63010535 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_implicit_api.yml @@ -0,0 +1,4 @@ +framework: + secret: s3cr3t + validation: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_multiple_static_methods.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_multiple_static_methods.yml new file mode 100644 index 0000000000000..6ca343351328a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_multiple_static_methods.yml @@ -0,0 +1,5 @@ +framework: + secret: s3cr3t + validation: + enabled: true + static_method: [loadFoo, loadBar] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_no_static_method.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_no_static_method.yml new file mode 100644 index 0000000000000..ca5214964259e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_no_static_method.yml @@ -0,0 +1,5 @@ +framework: + secret: s3cr3t + validation: + enabled: true + static_method: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index e217c569182d2..07a337abd116a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -15,6 +15,8 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Validator\Validation; abstract class FrameworkExtensionTest extends TestCase { @@ -30,7 +32,29 @@ public function testCsrfProtection() $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(1)); $this->assertEquals('_csrf', $container->getParameter('form.type_extension.csrf.field_name')); $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(2)); - $this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1))); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage CSRF protection needs sessions to be enabled. + */ + public function testCsrfProtectionNeedsSessionToBeEnabled() + { + $this->createContainerFromFile('csrf_needs_session'); + } + + public function testCsrfProtectionForFormsEnablesCsrfProtectionAutomatically() + { + $container = $this->createContainerFromFile('csrf'); + + $this->assertTrue($container->hasDefinition('security.csrf.token_manager')); + } + + public function testSecureRandomIsAvailableIfCsrfIsDisabled() + { + $container = $this->createContainerFromFile('csrf_disabled'); + + $this->assertTrue($container->hasDefinition('security.secure_random')); } public function testProxies() @@ -123,6 +147,22 @@ public function testNullSessionHandler() $this->assertNull($container->getDefinition('session.storage.php_bridge')->getArgument(0)); } + public function testRequest() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('request.add_request_formats_listener'), '->registerRequestConfiguration() loads request.xml'); + $listenerDef = $container->getDefinition('request.add_request_formats_listener'); + $this->assertEquals(array('csv' => array('text/csv', 'text/plain'), 'pdf' => array('application/pdf')), $listenerDef->getArgument(0)); + } + + public function testEmptyRequestFormats() + { + $container = $this->createContainerFromFile('request'); + + $this->assertFalse($container->hasDefinition('request.add_request_formats_listener'), '->registerRequestConfiguration() does not load request.xml when no request formats are defined'); + } + public function testTemplating() { $container = $this->createContainerFromFile('full'); @@ -197,7 +237,7 @@ public function testTranslator() ); $ref = new \ReflectionClass('Symfony\Component\Security\Core\SecurityContext'); $this->assertContains( - strtr(dirname(dirname($ref->getFileName())).'/Resources/translations/security.en.xlf', '/', DIRECTORY_SEPARATOR), + strtr(dirname($ref->getFileName()).'/Resources/translations/security.en.xlf', '/', DIRECTORY_SEPARATOR), $files, '->registerTranslatorConfiguration() finds Security translation resources' ); @@ -220,27 +260,55 @@ public function testValidation() { $container = $this->createContainerFromFile('full'); - $this->assertTrue($container->hasDefinition('validator'), '->registerValidationConfiguration() loads validator.xml'); - $this->assertTrue($container->hasDefinition('validator.mapping.loader.xml_files_loader'), '->registerValidationConfiguration() defines the XML loader'); - $this->assertTrue($container->hasDefinition('validator.mapping.loader.yaml_files_loader'), '->registerValidationConfiguration() defines the YAML loader'); - - $xmlFiles = $container->getParameter('validator.mapping.loader.xml_files_loader.mapping_files'); $ref = new \ReflectionClass('Symfony\Component\Form\Form'); - $this->assertContains( - realpath(dirname($ref->getFileName()).'/Resources/config/validation.xml'), - array_map('realpath', $xmlFiles), - '->registerValidationConfiguration() adds Form validation.xml to XML loader' - ); + $xmlMappings = array(realpath(dirname($ref->getFileName()).'/Resources/config/validation.xml')); + + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(7, $calls); + $this->assertSame('setConstraintValidatorFactory', $calls[0][0]); + $this->assertEquals(array(new Reference('validator.validator_factory')), $calls[0][1]); + $this->assertSame('setTranslator', $calls[1][0]); + $this->assertEquals(array(new Reference('translator')), $calls[1][1]); + $this->assertSame('setTranslationDomain', $calls[2][0]); + $this->assertSame(array('%validator.translation_domain%'), $calls[2][1]); + $this->assertSame('addXmlMappings', $calls[3][0]); + $this->assertSame(array($xmlMappings), $calls[3][1]); + $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->assertSame('setApiVersion', $calls[6][0]); + + if (PHP_VERSION_ID < 50309) { + $this->assertEquals(array(Validation::API_VERSION_2_4), $calls[6][1]); + } else { + $this->assertEquals(array(Validation::API_VERSION_2_5_BC), $calls[6][1]); + } } - public function testAnnotations() + public function testFullyConfiguredValidationService() { - if (!class_exists('Doctrine\\Common\\Version')) { - $this->markTestSkipped('Doctrine is not available.'); + if (!extension_loaded('apc')) { + $this->markTestSkipped('The apc extension is not available.'); } $container = $this->createContainerFromFile('full'); + $this->assertInstanceOf('Symfony\Component\Validator\ValidatorInterface', $container->get('validator')); + } + + public function testValidationService() + { + $container = $this->createContainerFromFile('validation_annotations'); + + $this->assertInstanceOf('Symfony\Component\Validator\ValidatorInterface', $container->get('validator')); + } + + 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')); } @@ -256,15 +324,14 @@ public function testValidationAnnotations() { $container = $this->createContainerFromFile('validation_annotations'); - $this->assertTrue($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->registerValidationConfiguration() defines the annotation loader'); - $loaders = $container->getDefinition('validator.mapping.loader.loader_chain')->getArgument(0); - $found = false; - foreach ($loaders as $loader) { - if ('validator.mapping.loader.annotation_loader' === (string) $loader) { - $found = true; - } - } - $this->assertTrue($found, 'validator.mapping.loader.annotation_loader is added to the loader chain.'); + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(7, $calls); + $this->assertSame('enableAnnotationMapping', $calls[4][0]); + $this->assertEquals(array(new Reference('annotation_reader')), $calls[4][1]); + $this->assertSame('addMethodMapping', $calls[5][0]); + $this->assertSame(array('loadValidatorMetadata'), $calls[5][1]); + // no cache this time } public function testValidationPaths() @@ -275,14 +342,149 @@ public function testValidationPaths() 'kernel.bundles' => array('TestBundle' => 'Symfony\Bundle\FrameworkBundle\Tests\TestBundle'), )); - $yamlArgs = $container->getParameter('validator.mapping.loader.yaml_files_loader.mapping_files'); - $this->assertCount(1, $yamlArgs); - $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.yml', $yamlArgs[0]); + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(8, $calls); + $this->assertSame('addXmlMappings', $calls[3][0]); + $this->assertSame('addYamlMappings', $calls[4][0]); + $this->assertSame('enableAnnotationMapping', $calls[5][0]); + $this->assertSame('addMethodMapping', $calls[6][0]); + $this->assertSame(array('loadValidatorMetadata'), $calls[6][1]); - $xmlArgs = $container->getParameter('validator.mapping.loader.xml_files_loader.mapping_files'); - $this->assertCount(2, $xmlArgs); - $this->assertStringEndsWith('Component'.DIRECTORY_SEPARATOR.'Form/Resources/config/validation.xml', $xmlArgs[0]); - $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.xml', $xmlArgs[1]); + $xmlMappings = $calls[3][1][0]; + $this->assertCount(2, $xmlMappings); + $this->assertStringEndsWith('Component'.DIRECTORY_SEPARATOR.'Form/Resources/config/validation.xml', $xmlMappings[0]); + $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.xml', $xmlMappings[1]); + + $yamlMappings = $calls[4][1][0]; + $this->assertCount(1, $yamlMappings); + $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.yml', $yamlMappings[0]); + } + + public function testValidationNoStaticMethod() + { + $container = $this->createContainerFromFile('validation_no_static_method'); + + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(5, $calls); + $this->assertSame('addXmlMappings', $calls[3][0]); + // no cache, no annotations, no static methods + } + + public function testValidation2Dot4Api() + { + $container = $this->createContainerFromFile('validation_2_4_api'); + + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(6, $calls); + $this->assertSame('addXmlMappings', $calls[3][0]); + $this->assertSame('addMethodMapping', $calls[4][0]); + $this->assertSame(array('loadValidatorMetadata'), $calls[4][1]); + $this->assertSame('setApiVersion', $calls[5][0]); + $this->assertSame(array(Validation::API_VERSION_2_4), $calls[5][1]); + $this->assertSame('Symfony\Component\Validator\ValidatorInterface', $container->getParameter('validator.class')); + // no cache, no annotations + } + + public function testValidation2Dot5Api() + { + $container = $this->createContainerFromFile('validation_2_5_api'); + + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(6, $calls); + $this->assertSame('addXmlMappings', $calls[3][0]); + $this->assertSame('addMethodMapping', $calls[4][0]); + $this->assertSame(array('loadValidatorMetadata'), $calls[4][1]); + $this->assertSame('setApiVersion', $calls[5][0]); + $this->assertSame(array(Validation::API_VERSION_2_5), $calls[5][1]); + $this->assertSame('Symfony\Component\Validator\Validator\ValidatorInterface', $container->getParameter('validator.class')); + // no cache, no annotations + } + + public function testValidation2Dot5BcApi() + { + $container = $this->createContainerFromFile('validation_2_5_bc_api'); + + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(6, $calls); + $this->assertSame('addXmlMappings', $calls[3][0]); + $this->assertSame('addMethodMapping', $calls[4][0]); + $this->assertSame(array('loadValidatorMetadata'), $calls[4][1]); + $this->assertSame('setApiVersion', $calls[5][0]); + $this->assertSame(array(Validation::API_VERSION_2_5_BC), $calls[5][1]); + $this->assertSame('Symfony\Component\Validator\ValidatorInterface', $container->getParameter('validator.class')); + // no cache, no annotations + } + + public function testValidationImplicitApi() + { + $container = $this->createContainerFromFile('validation_implicit_api'); + + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(6, $calls); + $this->assertSame('addXmlMappings', $calls[3][0]); + $this->assertSame('addMethodMapping', $calls[4][0]); + $this->assertSame(array('loadValidatorMetadata'), $calls[4][1]); + $this->assertSame('setApiVersion', $calls[5][0]); + // no cache, no annotations + + if (PHP_VERSION_ID < 50309) { + $this->assertSame(array(Validation::API_VERSION_2_4), $calls[5][1]); + } else { + $this->assertSame(array(Validation::API_VERSION_2_5_BC), $calls[5][1]); + } + } + + /** + * This feature is equivalent to the implicit api, only that the "api" + * key is explicitly set to "auto". + */ + public function testValidationAutoApi() + { + $container = $this->createContainerFromFile('validation_auto_api'); + + $calls = $container->getDefinition('validator.builder')->getMethodCalls(); + + $this->assertCount(6, $calls); + $this->assertSame('addXmlMappings', $calls[3][0]); + $this->assertSame('addMethodMapping', $calls[4][0]); + $this->assertSame(array('loadValidatorMetadata'), $calls[4][1]); + $this->assertSame('setApiVersion', $calls[5][0]); + // no cache, no annotations + + if (PHP_VERSION_ID < 50309) { + $this->assertSame(array(Validation::API_VERSION_2_4), $calls[5][1]); + } else { + $this->assertSame(array(Validation::API_VERSION_2_5_BC), $calls[5][1]); + } + } + + public function testFormsCanBeEnabledWithoutCsrfProtection() + { + $container = $this->createContainerFromFile('form_no_csrf'); + + $this->assertFalse($container->getParameter('form.type_extension.csrf.enabled')); + } + + public function testFormCsrfFieldNameCanBeSetUnderCsrfSettings() + { + $container = $this->createContainerFromFile('form_csrf_sets_field_name'); + + $this->assertTrue($container->getParameter('form.type_extension.csrf.enabled')); + $this->assertEquals('_custom', $container->getParameter('form.type_extension.csrf.field_name')); + } + + public function testFormCsrfFieldNameUnderFormSettingsTakesPrecedence() + { + $container = $this->createContainerFromFile('form_csrf_under_form_sets_field_name'); + + $this->assertTrue($container->getParameter('form.type_extension.csrf.enabled')); + $this->assertEquals('_custom_form', $container->getParameter('form.type_extension.csrf.field_name')); } protected function createContainer(array $data = array()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php index b8dcefc558021..43070c00c9830 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php @@ -22,11 +22,4 @@ protected function loadFromFile(ContainerBuilder $container, $file) $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/Fixtures/yml')); $loader->load($file.'.yml'); } - - public function testCsrfProtectionShouldBeEnabledByDefault() - { - $container = $this->createContainerFromFile('csrf'); - - $this->assertTrue($container->getParameter('form.type_extension.csrf.enabled')); - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.json new file mode 100644 index 0000000000000..283ed13fa3302 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.json @@ -0,0 +1,4 @@ +{ + "service": "service_1", + "public": true +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.md new file mode 100644 index 0000000000000..ec63107b93b0f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.md @@ -0,0 +1,2 @@ +- Service: `service_1` +- Public: yes diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt new file mode 100644 index 0000000000000..3d40ba0084340 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt @@ -0,0 +1 @@ +This service is an alias for the service service_1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.xml new file mode 100644 index 0000000000000..759f8a22aa1dc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.xml @@ -0,0 +1,2 @@ + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.json new file mode 100644 index 0000000000000..6998dae2828a8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.json @@ -0,0 +1,4 @@ +{ + "service": "service_2", + "public": false +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.md new file mode 100644 index 0000000000000..f2a46087c162e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.md @@ -0,0 +1,2 @@ +- Service: `service_2` +- Public: no diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt new file mode 100644 index 0000000000000..df99e817c96b5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt @@ -0,0 +1 @@ +This service is an alias for the service service_2 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.xml new file mode 100644 index 0000000000000..847050b33a428 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.xml @@ -0,0 +1,2 @@ + + 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 new file mode 100644 index 0000000000000..33c5ed71d422f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json @@ -0,0 +1,27 @@ +{ + "definitions": { + "definition_1": { + "class": "Full\\Qualified\\Class1", + "scope": "container", + "public": true, + "synthetic": false, + "file": null, + "tags": [ + + ] + } + }, + "aliases": { + "alias_1": { + "service": "service_1", + "public": true + }, + "alias_2": { + "service": "service_2", + "public": false + } + }, + "services": { + "service_container": "Symfony\\Component\\DependencyInjection\\ContainerBuilder" + } +} 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 new file mode 100644 index 0000000000000..196757b13b7a2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md @@ -0,0 +1,35 @@ +Public services +=============== + +Definitions +----------- + +definition_1 +~~~~~~~~~~~~ + +- Class: `Full\Qualified\Class1` +- Scope: `container` +- Public: yes +- Synthetic: no + + +Aliases +------- + +alias_1 +~~~~~~~ + +- Service: `service_1` +- Public: yes + +alias_2 +~~~~~~~ + +- Service: `service_2` +- Public: no + + +Services +-------- + +- `service_container`: `Symfony\Component\DependencyInjection\ContainerBuilder` 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 new file mode 100644 index 0000000000000..58380fdf4d218 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.txt @@ -0,0 +1,6 @@ +[container] Public services + Service ID Scope Class name + alias_1 n/a alias for "service_1" + alias_2 n/a alias for "service_2" + definition_1 container Full\Qualified\Class1 + service_container Symfony\Component\DependencyInjection\ContainerBuilder 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 new file mode 100644 index 0000000000000..16df428799fd0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml @@ -0,0 +1,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 new file mode 100644 index 0000000000000..6dd5929838f11 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json @@ -0,0 +1,55 @@ +{ + "definitions": { + "definition_1": { + "class": "Full\\Qualified\\Class1", + "scope": "container", + "public": true, + "synthetic": false, + "file": null, + "tags": [ + + ] + }, + "definition_2": { + "class": "Full\\Qualified\\Class2", + "scope": "container", + "public": false, + "synthetic": true, + "file": "\/path\/to\/file", + "tags": [ + { + "name": "tag1", + "parameters": { + "attr1": "val1", + "attr2": "val2" + } + }, + { + "name": "tag1", + "parameters": { + "attr3": "val3" + } + }, + { + "name": "tag2", + "parameters": [ + + ] + } + ] + } + }, + "aliases": { + "alias_1": { + "service": "service_1", + "public": true + }, + "alias_2": { + "service": "service_2", + "public": false + } + }, + "services": { + "service_container": "Symfony\\Component\\DependencyInjection\\ContainerBuilder" + } +} 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 new file mode 100644 index 0000000000000..33cb0a93a74c9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md @@ -0,0 +1,50 @@ +Public and private services +=========================== + +Definitions +----------- + +definition_1 +~~~~~~~~~~~~ + +- Class: `Full\Qualified\Class1` +- Scope: `container` +- Public: yes +- Synthetic: no + +definition_2 +~~~~~~~~~~~~ + +- Class: `Full\Qualified\Class2` +- Scope: `container` +- Public: no +- Synthetic: yes +- File: `/path/to/file` +- Tag: `tag1` + - Attr1: val1 + - Attr2: val2 +- Tag: `tag1` + - Attr3: val3 +- Tag: `tag2` + + +Aliases +------- + +alias_1 +~~~~~~~ + +- Service: `service_1` +- Public: yes + +alias_2 +~~~~~~~ + +- Service: `service_2` +- Public: no + + +Services +-------- + +- `service_container`: `Symfony\Component\DependencyInjection\ContainerBuilder` 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 new file mode 100644 index 0000000000000..98a1f27aeb94e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt @@ -0,0 +1,8 @@ +[container] Public and private services + Service ID Scope Class name + alias_1 n/a alias for "service_1" + alias_2 n/a alias for "service_2" + definition_1 container Full\Qualified\Class1 + definition_2 container Full\Qualified\Class2 + service_container Symfony\Component\DependencyInjection\ContainerBuilder + \ No newline at end of file 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 new file mode 100644 index 0000000000000..4636803bfb2cd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml @@ -0,0 +1,19 @@ + + + + + + + + + val1 + val2 + + + val3 + + + + + + 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 new file mode 100644 index 0000000000000..d9a351b90b1ec --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json @@ -0,0 +1,38 @@ +{ + "definitions": { + "definition_2": { + "class": "Full\\Qualified\\Class2", + "scope": "container", + "public": false, + "synthetic": true, + "file": "\/path\/to\/file", + "tags": [ + { + "name": "tag1", + "parameters": { + "attr1": "val1", + "attr2": "val2" + } + }, + { + "name": "tag1", + "parameters": { + "attr3": "val3" + } + }, + { + "name": "tag2", + "parameters": [ + + ] + } + ] + } + }, + "aliases": [ + + ], + "services": [ + + ] +} 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 new file mode 100644 index 0000000000000..2b32f9e84316d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md @@ -0,0 +1,20 @@ +Public and private services with tag `tag1` +=========================================== + +Definitions +----------- + +definition_2 +~~~~~~~~~~~~ + +- Class: `Full\Qualified\Class2` +- Scope: `container` +- Public: no +- Synthetic: yes +- File: `/path/to/file` +- Tag: `tag1` + - Attr1: val1 + - Attr2: val2 +- Tag: `tag1` + - Attr3: val3 +- Tag: `tag2` 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 new file mode 100644 index 0000000000000..f2d6135b0a7b3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.txt @@ -0,0 +1,4 @@ +[container] Public and private services with tag tag1 + Service ID attr1 attr2 attr3 Scope Class name + definition_2 val1 val2 container Full\Qualified\Class2 + " val3 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 new file mode 100644 index 0000000000000..c755e70ce76f9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml @@ -0,0 +1,15 @@ + + + + + + val1 + val2 + + + val3 + + + + + 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 new file mode 100644 index 0000000000000..e0a3c0f31ca7a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json @@ -0,0 +1,20 @@ +{ + "tag1": [ + { + "class": "Full\\Qualified\\Class2", + "scope": "container", + "public": false, + "synthetic": true, + "file": "\/path\/to\/file" + } + ], + "tag2": [ + { + "class": "Full\\Qualified\\Class2", + "scope": "container", + "public": false, + "synthetic": true, + "file": "\/path\/to\/file" + } + ] +} 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 new file mode 100644 index 0000000000000..b69cf69ac7911 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md @@ -0,0 +1,27 @@ +Container tags +============== + +tag1 +---- + +definition_2 +~~~~~~~~~~~~ + +- Class: `Full\Qualified\Class2` +- Scope: `container` +- Public: no +- Synthetic: yes +- File: `/path/to/file` + + +tag2 +---- + +definition_2 +~~~~~~~~~~~~ + +- Class: `Full\Qualified\Class2` +- Scope: `container` +- Public: no +- Synthetic: yes +- File: `/path/to/file` 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 new file mode 100644 index 0000000000000..791c1d95c3c9e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.txt @@ -0,0 +1,6 @@ +[container] Tagged services +[tag] tag1 +definition_2 + +[tag] tag2 +definition_2 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 new file mode 100644 index 0000000000000..b0f6244487dae --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json new file mode 100644 index 0000000000000..00a553ce23b7b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json @@ -0,0 +1,10 @@ +{ + "class": "Full\\Qualified\\Class1", + "scope": "container", + "public": true, + "synthetic": false, + "file": null, + "tags": [ + + ] +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md new file mode 100644 index 0000000000000..49005b12a4a47 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md @@ -0,0 +1,4 @@ +- Class: `Full\Qualified\Class1` +- Scope: `container` +- Public: yes +- Synthetic: no diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt new file mode 100644 index 0000000000000..74aabca95a27b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt @@ -0,0 +1,7 @@ +Service Id - +Class Full\Qualified\Class1 +Tags - +Scope container +Public yes +Synthetic no +Required 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 new file mode 100644 index 0000000000000..75d0820244579 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml @@ -0,0 +1,2 @@ + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json new file mode 100644 index 0000000000000..d31f146a6ad2b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json @@ -0,0 +1,28 @@ +{ + "class": "Full\\Qualified\\Class2", + "scope": "container", + "public": false, + "synthetic": true, + "file": "\/path\/to\/file", + "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/definition_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md new file mode 100644 index 0000000000000..521b496e07609 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md @@ -0,0 +1,11 @@ +- Class: `Full\Qualified\Class2` +- Scope: `container` +- Public: no +- Synthetic: yes +- File: `/path/to/file` +- Tag: `tag1` + - Attr1: val1 + - Attr2: val2 +- Tag: `tag1` + - Attr3: val3 +- Tag: `tag2` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt new file mode 100644 index 0000000000000..06b6c327cf521 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt @@ -0,0 +1,10 @@ +Service Id - +Class Full\Qualified\Class2 +Tags + - tag1 (attr1: val1, attr2: val2) + - tag1 (attr3: val3) + - tag2 () +Scope container +Public no +Synthetic yes +Required File /path/to/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 new file mode 100644 index 0000000000000..dd3e2e06d7174 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml @@ -0,0 +1,13 @@ + + + + + val1 + val2 + + + val3 + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.json new file mode 100644 index 0000000000000..069fdbc5d396f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.json @@ -0,0 +1,3 @@ +{ + "database_name": "symfony" +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.md new file mode 100644 index 0000000000000..4c67978f68347 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.md @@ -0,0 +1,4 @@ +database_name +============= + +symfony diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.txt new file mode 100644 index 0000000000000..a1435083911ce --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.txt @@ -0,0 +1 @@ +symfony diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.xml new file mode 100644 index 0000000000000..8465522c1bc2a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameter.xml @@ -0,0 +1,2 @@ + +symfony diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.json new file mode 100644 index 0000000000000..686cbc9e52209 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.json @@ -0,0 +1,10 @@ +{ + "array": [ + 12, + "Hello world!", + true + ], + "boolean": true, + "integer": 12, + "string": "Hello world!" +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.md new file mode 100644 index 0000000000000..2dfe5d640b858 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.md @@ -0,0 +1,7 @@ +Container parameters +==================== + +- `array`: `[12,"Hello world!",true]` +- `boolean`: `true` +- `integer`: `12` +- `string`: `Hello world!` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.txt new file mode 100644 index 0000000000000..1c9a2739ca63c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.txt @@ -0,0 +1,7 @@ +[container] List of 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/parameters_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.xml new file mode 100644 index 0000000000000..e97f895730139 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/parameters_1.xml @@ -0,0 +1,7 @@ + + + [12,"Hello world!",true] + true + 12 + Hello world! + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.json new file mode 100644 index 0000000000000..7da10ed861438 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.json @@ -0,0 +1,19 @@ +{ + "path": "\/hello\/{name}", + "host": "localhost", + "scheme": "http|https", + "method": "GET|HEAD", + "class": "Symfony\\Component\\Routing\\Route", + "defaults": { + "name": "Joseph" + }, + "requirements": { + "name": "[a-z]+" + }, + "options": { + "compiler_class": "Symfony\\Component\\Routing\\RouteCompiler", + "opt1": "val1", + "opt2": "val2" + }, + "pathRegex": "#^\/hello(?:\/(?P[a-z]+))?$#s" +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.md new file mode 100644 index 0000000000000..4ac00a8929755 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.md @@ -0,0 +1,9 @@ +- Path: /hello/{name} +- Host: localhost +- Scheme: http|https +- Method: GET|HEAD +- Class: Symfony\Component\Routing\Route +- Defaults: + - `name`: Joseph +- Requirements: + - `name`: [a-z]+ \ 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 new file mode 100644 index 0000000000000..2552c1ed88993 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt @@ -0,0 +1,12 @@ +Path /hello/{name} +Host localhost +Scheme http|https +Method GET|HEAD +Class Symfony\Component\Routing\Route +Defaults name: Joseph +Requirements name: [a-z]+ +Options compiler_class: Symfony\Component\Routing\RouteCompiler + opt1: val1 + opt2: val2 +Path-Regex #^/hello(?:/(?P[a-z]+))?$#s +Host-Regex #^localhost$#s \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.xml new file mode 100644 index 0000000000000..164282c5f69ad --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.xml @@ -0,0 +1,20 @@ + + + /hello/{name} + localhost + http + https + GET + HEAD + + Joseph + + + [a-z]+ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.json new file mode 100644 index 0000000000000..276f8ca42a0a4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.json @@ -0,0 +1,17 @@ +{ + "path": "\/name\/add", + "host": "localhost", + "scheme": "http|https", + "method": "PUT|POST", + "class": "Symfony\\Component\\Routing\\Route", + "defaults": [ + + ], + "requirements": "NO CUSTOM", + "options": { + "compiler_class": "Symfony\\Component\\Routing\\RouteCompiler", + "opt1": "val1", + "opt2": "val2" + }, + "pathRegex": "#^\/name\/add$#s" +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.md new file mode 100644 index 0000000000000..c19d75f4f3b13 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.md @@ -0,0 +1,7 @@ +- Path: /name/add +- Host: localhost +- Scheme: http|https +- Method: PUT|POST +- Class: Symfony\Component\Routing\Route +- Defaults: NONE +- Requirements: NONE \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt new file mode 100644 index 0000000000000..99119b6cc4e90 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt @@ -0,0 +1,12 @@ +Path /name/add +Host localhost +Scheme http|https +Method PUT|POST +Class Symfony\Component\Routing\Route +Defaults +Requirements +Options compiler_class: Symfony\Component\Routing\RouteCompiler + opt1: val1 + opt2: val2 +Path-Regex #^/name/add$#s +Host-Regex #^localhost$#s \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.xml new file mode 100644 index 0000000000000..333eeaa1b4166 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.xml @@ -0,0 +1,14 @@ + + + /name/add + localhost + http + https + PUT + POST + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.json new file mode 100644 index 0000000000000..0cd1610f20ae8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.json @@ -0,0 +1,38 @@ +{ + "route_1": { + "path": "\/hello\/{name}", + "host": "localhost", + "scheme": "http|https", + "method": "GET|HEAD", + "class": "Symfony\\Component\\Routing\\Route", + "defaults": { + "name": "Joseph" + }, + "requirements": { + "name": "[a-z]+" + }, + "options": { + "compiler_class": "Symfony\\Component\\Routing\\RouteCompiler", + "opt1": "val1", + "opt2": "val2" + }, + "pathRegex": "#^\/hello(?:\/(?P[a-z]+))?$#s" + }, + "route_2": { + "path": "\/name\/add", + "host": "localhost", + "scheme": "http|https", + "method": "PUT|POST", + "class": "Symfony\\Component\\Routing\\Route", + "defaults": [ + + ], + "requirements": "NO CUSTOM", + "options": { + "compiler_class": "Symfony\\Component\\Routing\\RouteCompiler", + "opt1": "val1", + "opt2": "val2" + }, + "pathRegex": "#^\/name\/add$#s" + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.md new file mode 100644 index 0000000000000..a148c23210bad --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.md @@ -0,0 +1,24 @@ +route_1 +------- + +- Path: /hello/{name} +- Host: localhost +- Scheme: http|https +- Method: GET|HEAD +- Class: Symfony\Component\Routing\Route +- Defaults: + - `name`: Joseph +- Requirements: + - `name`: [a-z]+ + + +route_2 +------- + +- Path: /name/add +- Host: localhost +- Scheme: http|https +- Method: PUT|POST +- Class: Symfony\Component\Routing\Route +- Defaults: NONE +- Requirements: NONE 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 new file mode 100644 index 0000000000000..e0ade43e2afd3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.txt @@ -0,0 +1,5 @@ +[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 + \ No newline at end of file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.xml new file mode 100644 index 0000000000000..aa5adb482aa76 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.xml @@ -0,0 +1,35 @@ + + + + /hello/{name} + localhost + http + https + GET + HEAD + + Joseph + + + [a-z]+ + + + + + + + + + /name/add + localhost + http + https + PUT + POST + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php index 96bc067fe5a8d..bf22e63370139 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SessionController.php @@ -11,15 +11,15 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\DependencyInjection\ContainerAware; class SessionController extends ContainerAware { - public function welcomeAction($name = null) + public function welcomeAction(Request $request, $name = null) { - $request = $this->container->get('request'); $session = $request->getSession(); // new session case @@ -40,25 +40,23 @@ public function welcomeAction($name = null) return new Response(sprintf('Welcome back %s, nice to meet you.', $name)); } - public function logoutAction() + public function logoutAction(Request $request) { - $request = $this->container->get('request')->getSession('session')->invalidate(); + $request->getSession('session')->invalidate(); return new Response('Session cleared.'); } - public function setFlashAction($message) + public function setFlashAction(Request $request, $message) { - $request = $this->container->get('request'); $session = $request->getSession(); $session->getFlashBag()->set('notice', $message); return new RedirectResponse($this->container->get('router')->generate('session_showflash')); } - public function showFlashAction() + public function showFlashAction(Request $request) { - $request = $this->container->get('request'); $session = $request->getSession(); if ($session->getFlashBag()->has('notice')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/WebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/WebTestCase.php index 6b8fe0ace6550..50d4cfa4db269 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/WebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/WebTestCase.php @@ -23,15 +23,6 @@ public static function assertRedirect($response, $location) self::assertEquals('http://localhost'.$location, $response->headers->get('Location')); } - protected function setUp() - { - if (!class_exists('Twig_Environment')) { - $this->markTestSkipped('Twig is not available.'); - } - - parent::setUp(); - } - protected function deleteTmpDir($testCase) { if (!file_exists($dir = sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$testCase)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RedirectableUrlMatcherTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RedirectableUrlMatcherTest.php index 6b31fb2e49211..70cf0fcffd031 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RedirectableUrlMatcherTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RedirectableUrlMatcherTest.php @@ -38,7 +38,7 @@ public function testRedirectWhenNoSlash() ); } - public function testSchemeRedirect() + public function testSchemeRedirectBC() { $coll = new RouteCollection(); $coll->add('foo', new Route('/foo', array(), array('_scheme' => 'https'))); @@ -57,4 +57,24 @@ public function testSchemeRedirect() $matcher->match('/foo') ); } + + public function testSchemeRedirect() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https'))); + + $matcher = new RedirectableUrlMatcher($coll, $context = new RequestContext()); + + $this->assertEquals(array( + '_controller' => 'Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction', + 'path' => '/foo', + 'permanent' => true, + 'scheme' => 'https', + 'httpPort' => $context->getHttpPort(), + 'httpsPort' => $context->getHttpsPort(), + '_route' => 'foo', + ), + $matcher->match('/foo') + ); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/DelegatingEngineTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/DelegatingEngineTest.php new file mode 100644 index 0000000000000..c5d2d8bbebede --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/DelegatingEngineTest.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\FrameworkBundle\Tests\Templating; + +use Symfony\Bundle\FrameworkBundle\Templating\DelegatingEngine; + +class DelegatingEngineTest extends \PHPUnit_Framework_TestCase +{ + public function testSupportsRetrievesEngineFromTheContainer() + { + $container = $this->getContainerMock(array( + 'engine.first' => $this->getEngineMock('template.php', false), + 'engine.second' => $this->getEngineMock('template.php', true), + )); + + $delegatingEngine = new DelegatingEngine($container, array('engine.first', 'engine.second')); + + $this->assertTrue($delegatingEngine->supports('template.php')); + } + + public function testGetExistingEngine() + { + $firstEngine = $this->getEngineMock('template.php', false); + $secondEngine = $this->getEngineMock('template.php', true); + $container = $this->getContainerMock(array( + 'engine.first' => $firstEngine, + 'engine.second' => $secondEngine, + )); + + $delegatingEngine = new DelegatingEngine($container, array('engine.first', 'engine.second')); + + $this->assertSame($secondEngine, $delegatingEngine->getEngine('template.php', array('foo' => 'bar'))); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage No engine is able to work with the template "template.php" + */ + public function testGetInvalidEngine() + { + $firstEngine = $this->getEngineMock('template.php', false); + $secondEngine = $this->getEngineMock('template.php', false); + $container = $this->getContainerMock(array( + 'engine.first' => $firstEngine, + 'engine.second' => $secondEngine, + )); + + $delegatingEngine = new DelegatingEngine($container, array('engine.first', 'engine.second')); + $delegatingEngine->getEngine('template.php', array('foo' => 'bar')); + } + + public function testRenderResponseWithFrameworkEngine() + { + $response = $this->getMock('Symfony\Component\HttpFoundation\Response'); + $engine = $this->getFrameworkEngineMock('template.php', true); + $engine->expects($this->once()) + ->method('renderResponse') + ->with('template.php', array('foo' => 'bar')) + ->will($this->returnValue($response)); + $container = $this->getContainerMock(array('engine' => $engine)); + + $delegatingEngine = new DelegatingEngine($container, array('engine')); + + $this->assertSame($response, $delegatingEngine->renderResponse('template.php', array('foo' => 'bar'))); + } + + public function testRenderResponseWithTemplatingEngine() + { + $engine = $this->getEngineMock('template.php', true); + $container = $this->getContainerMock(array('engine' => $engine)); + $delegatingEngine = new DelegatingEngine($container, array('engine')); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $delegatingEngine->renderResponse('template.php', array('foo' => 'bar'))); + } + + private function getEngineMock($template, $supports) + { + $engine = $this->getMock('Symfony\Component\Templating\EngineInterface'); + + $engine->expects($this->once()) + ->method('supports') + ->with($template) + ->will($this->returnValue($supports)); + + return $engine; + } + + private function getFrameworkEngineMock($template, $supports) + { + $engine = $this->getMock('Symfony\Bundle\FrameworkBundle\Templating\EngineInterface'); + + $engine->expects($this->once()) + ->method('supports') + ->with($template) + ->will($this->returnValue($supports)); + + return $engine; + } + + private function getContainerMock($services) + { + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + + $i = 0; + foreach ($services as $id => $service) { + $container->expects($this->at($i++)) + ->method('get') + ->with($id) + ->will($this->returnValue($service)); + } + + return $container; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index 70df59acbeada..0c2904a68aacb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @@ -29,19 +29,6 @@ class FormHelperDivLayoutTest extends AbstractDivLayoutTest */ protected $engine; - protected function setUp() - { - if (!class_exists('Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper')) { - $this->markTestSkipped('The "FrameworkBundle" is not available'); - } - - if (!class_exists('Symfony\Component\Templating\PhpEngine')) { - $this->markTestSkipped('The "Templating" component is not available'); - } - - parent::setUp(); - } - protected function getExtensions() { // should be moved to the Form component once absolute file paths are supported @@ -59,7 +46,7 @@ protected function getExtensions() )); return array_merge(parent::getExtensions(), array( - new TemplatingExtension($this->engine, $this->csrfProvider, array( + new TemplatingExtension($this->engine, $this->csrfTokenManager, array( 'FrameworkBundle:Form', )), )); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php index 96c56b65064e1..e7e6e60bfe4b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @@ -29,19 +29,6 @@ class FormHelperTableLayoutTest extends AbstractTableLayoutTest */ protected $engine; - protected function setUp() - { - if (!class_exists('Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper')) { - $this->markTestSkipped('The "FrameworkBundle" is not available'); - } - - if (!class_exists('Symfony\Component\Templating\PhpEngine')) { - $this->markTestSkipped('The "Templating" component is not available'); - } - - parent::setUp(); - } - protected function getExtensions() { // should be moved to the Form component once absolute file paths are supported @@ -59,7 +46,7 @@ protected function getExtensions() )); return array_merge(parent::getExtensions(), array( - new TemplatingExtension($this->engine, $this->csrfProvider, array( + new TemplatingExtension($this->engine, $this->csrfTokenManager, array( 'FrameworkBundle:Form', 'FrameworkBundle:FormTable', )), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/RequestHelperTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/RequestHelperTest.php index d3d4f22385c24..d626e63e41cfa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/RequestHelperTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/RequestHelperTest.php @@ -12,26 +12,24 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Bundle\FrameworkBundle\Templating\Helper\RequestHelper; class RequestHelperTest extends \PHPUnit_Framework_TestCase { - protected $request; + protected $requestStack; protected function setUp() { - $this->request = new Request(); - $this->request->initialize(array('foobar' => 'bar')); - } - - protected function tearDown() - { - $this->request = null; + $this->requestStack = new RequestStack(); + $request = new Request(); + $request->initialize(array('foobar' => 'bar')); + $this->requestStack->push($request); } public function testGetParameter() { - $helper = new RequestHelper($this->request); + $helper = new RequestHelper($this->requestStack); $this->assertEquals('bar', $helper->getParameter('foobar')); $this->assertEquals('foo', $helper->getParameter('bar', 'foo')); @@ -41,14 +39,14 @@ public function testGetParameter() public function testGetLocale() { - $helper = new RequestHelper($this->request); + $helper = new RequestHelper($this->requestStack); $this->assertEquals('en', $helper->getLocale()); } public function testGetName() { - $helper = new RequestHelper($this->request); + $helper = new RequestHelper($this->requestStack); $this->assertEquals('request', $helper->getName()); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php index 0efecf0c721ef..e165a429a5ff8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php @@ -1,2 +1,4 @@ -humanize($name); } ?> +humanize($name); +} ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/StopwatchHelperTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/StopwatchHelperTest.php new file mode 100644 index 0000000000000..1a0ff5f548cee --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/StopwatchHelperTest.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\Bundle\FrameworkBundle\Tests\Templating\Helper; + +use Symfony\Bundle\FrameworkBundle\Templating\Helper\StopwatchHelper; + +class StopwatchHelperTest extends \PHPUnit_Framework_TestCase +{ + public function testDevEnvironment() + { + $stopwatch = $this->getMock('Symfony\Component\Stopwatch\Stopwatch'); + $stopwatch->expects($this->once()) + ->method('start') + ->with('foo'); + + $helper = new StopwatchHelper($stopwatch); + $helper->start('foo'); + } + + public function testProdEnvironment() + { + $helper = new StopwatchHelper(null); + + try { + $helper->start('foo'); + } catch (\BadMethodCallException $e) { + $this->fail('Assumed stopwatch is not called when not provided'); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php index 476b398e61b13..b5eeb901342db 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/PhpEngineTest.php @@ -14,6 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Templating\PhpEngine; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\Templating\TemplateNameParser; @@ -37,7 +38,7 @@ public function testEvaluateWithoutAvailableRequest() $loader = $this->getMockForAbstractClass('Symfony\Component\Templating\Loader\Loader'); $engine = new PhpEngine(new TemplateNameParser(), $container, $loader, new GlobalVariables($container)); - $container->set('request', null); + $container->set('request_stack', null); $globals = $engine->getGlobals(); $this->assertEmpty($globals['app']->getRequest()); @@ -63,11 +64,13 @@ public function testGetInvalidHelper() protected function getContainer() { $container = new Container(); - $request = new Request(); $session = new Session(new MockArraySessionStorage()); + $request = new Request(); + $stack = new RequestStack(); + $stack->push($request); $request->setSession($session); - $container->set('request', $request); + $container->set('request_stack', $stack); return $container; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php index 9e2981f5e7046..e9d1406e069f7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php @@ -13,7 +13,6 @@ use Symfony\Bundle\FrameworkBundle\Templating\TimedPhpEngine; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\Templating\TemplateNameParser; use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index ab4818703f78c..8f81d30904862 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Translation; use Symfony\Bundle\FrameworkBundle\Translation\Translator; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Translation\MessageSelector; @@ -102,43 +103,46 @@ public function testTransWithCachingWithInvalidLocale() $translator->trans('foo'); } - public function testGetLocale() + /** + * @dataProvider getGetLocaleData + */ + public function testGetLocale($inRequestScope) { - $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); - - $request - ->expects($this->once()) - ->method('getLocale') - ->will($this->returnValue('en')) - ; + $requestStack = new RequestStack(); + if ($inRequestScope) { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request + ->expects($this->any()) + ->method('getLocale') + ->will($this->returnValue('en')) + ; + + $requestStack->push($request); + } $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); - - $container - ->expects($this->exactly(2)) - ->method('isScopeActive') - ->with('request') - ->will($this->onConsecutiveCalls(false, true)) - ; - - $container - ->expects($this->once()) - ->method('has') - ->with('request') - ->will($this->returnValue(true)) - ; - $container ->expects($this->once()) ->method('get') - ->with('request') - ->will($this->returnValue($request)) + ->with('request_stack') + ->will($this->returnValue($requestStack)) ; $translator = new Translator($container, new MessageSelector()); - $this->assertNull($translator->getLocale()); - $this->assertSame('en', $translator->getLocale()); + if ($inRequestScope) { + $this->assertSame('en', $translator->getLocale()); + } else { + $this->assertNull($translator->getLocale()); + } + } + + public function getGetLocaleData() + { + return array( + array(false), + array(true), + ); } public function testGetLocaleWithInvalidLocale() @@ -146,7 +150,7 @@ public function testGetLocaleWithInvalidLocale() $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); $request - ->expects($this->once()) + ->expects($this->any()) ->method('getLocale') ->will($this->returnValue('foo bar')) ; @@ -156,27 +160,15 @@ public function testGetLocaleWithInvalidLocale() ->will($this->returnValue('en-US')) ; - $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); - - $container - ->expects($this->once()) - ->method('isScopeActive') - ->with('request') - ->will($this->returnValue(true)) - ; + $requestStack = new RequestStack(); + $requestStack->push($request); + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); $container ->expects($this->once()) - ->method('has') - ->with('request') - ->will($this->returnValue(true)) - ; - - $container - ->expects($this->any()) ->method('get') - ->with('request') - ->will($this->returnValue($request)) + ->with('request_stack') + ->will($this->returnValue($requestStack)) ; $translator = new Translator($container, new MessageSelector()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index ffc050fb4857c..8592e992c3b20 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -24,7 +24,10 @@ class Translator extends BaseTranslator { protected $container; - protected $options; + protected $options = array( + 'cache_dir' => null, + 'debug' => false, + ); protected $loaderIds; /** @@ -47,11 +50,6 @@ public function __construct(ContainerInterface $container, MessageSelector $sele $this->container = $container; $this->loaderIds = $loaderIds; - $this->options = array( - 'cache_dir' => null, - 'debug' => false, - ); - // check option names if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new \InvalidArgumentException(sprintf('The Translator does not support the following options: \'%s\'.', implode('\', \'', $diff))); @@ -67,11 +65,12 @@ public function __construct(ContainerInterface $container, MessageSelector $sele */ public function getLocale() { - if (null === $this->locale && $this->container->isScopeActive('request') && $this->container->has('request')) { + if (null === $this->locale && $request = $this->container->get('request_stack')->getCurrentRequest()) { + $this->locale = $request->getLocale(); try { - $this->setLocale($this->container->get('request')->getLocale()); + $this->setLocale($request->getLocale()); } catch (\InvalidArgumentException $e) { - $this->setLocale($this->container->get('request')->getDefaultLocale()); + $this->setLocale($request->getDefaultLocale()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 17318f23d759d..b333609e3d47e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -17,37 +17,42 @@ ], "require": { "php": ">=5.3.3", - "symfony/dependency-injection" : "~2.3", - "symfony/config" : "~2.3,>=2.3.12", - "symfony/event-dispatcher": "~2.1", - "symfony/http-foundation": "~2.3,>=2.3.19", - "symfony/http-kernel": "~2.3,>=2.3.22", + "symfony/dependency-injection" : "~2.3,>=2.3.3", + "symfony/config" : "~2.4", + "symfony/event-dispatcher": "~2.5", + "symfony/http-foundation": "~2.4,>=2.4.9", + "symfony/http-kernel": "~2.5,>=2.5.7", "symfony/filesystem": "~2.3", "symfony/routing": "~2.2", + "symfony/security-core": "~2.4", + "symfony/security-csrf": "~2.4", "symfony/stopwatch": "~2.3", "symfony/templating": "~2.1", "symfony/translation": "~2.3,>=2.3.19", - "doctrine/common": "~2.2" + "doctrine/annotations": "~1.0" }, "require-dev": { "symfony/browser-kit": "~2.3", - "symfony/console": "~2.3", + "symfony/console": "~2.4,>=2.4.8", "symfony/css-selector": "~2.0,>=2.0.5", "symfony/dom-crawler": "~2.0,>=2.0.5", "symfony/finder": "~2.0,>=2.0.5", "symfony/locale": "~2.0,>=2.0.5", - "symfony/security": "~2.3", - "symfony/form": "~2.3.0,>=2.3.5", + "symfony/security": "~2.4", + "symfony/form": "2.5.*", "symfony/class-loader": "~2.1", + "symfony/expression-language": "~2.4", "symfony/process": "~2.0,>=2.0.5", - "symfony/validator": "~2.1", + "symfony/validator": "~2.5", "symfony/yaml": "~2.0,>=2.0.5" }, "suggest": { - "symfony/console": "", - "symfony/finder": "", - "symfony/form": "", - "symfony/validator": "" + "symfony/console": "For using the console commands", + "symfony/finder": "For using the translation loader and cache warmer", + "symfony/form": "For using forms", + "symfony/validator": "For using validation", + "symfony/yaml": "For using the config:debug and yaml:lint commands", + "doctrine/cache": "For using alternative cache drivers" }, "autoload": { "psr-0": { "Symfony\\Bundle\\FrameworkBundle\\": "" } @@ -56,7 +61,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 724254de78b4d..0791bcbc798b8 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,15 @@ CHANGELOG ========= +2.4.0 +----- + + * Added 'host' option to firewall configuration + * Added 'csrf_token_generator' and 'csrf_token_id' options to firewall logout + listener configuration to supercede/alias 'csrf_provider' and 'intention' + respectively + * Moved 'security.secure_random' service configuration to FrameworkBundle + 2.3.0 ----- @@ -74,9 +83,9 @@ CHANGELOG logout: path: /logout_path target: / - csrf_parameter: _csrf_token # Optional (defaults to "_csrf_token") - csrf_provider: form.csrf_provider # Required to enable protection - intention: logout # Optional (defaults to "logout") + csrf_parameter: _csrf_token # Optional (defaults to "_csrf_token") + csrf_provider: security.csrf.token_generator # Required to enable protection + intention: logout # Optional (defaults to "logout") ``` If the LogoutListener has CSRF protection enabled but cannot validate a token, diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index b06a6dc77bd9c..3f25c3da03e4a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -64,7 +65,7 @@ public function getConfigTreeBuilder() ->arrayNode('access_decision_manager') ->addDefaultsIfNotSet() ->children() - ->scalarNode('strategy')->defaultValue('affirmative')->end() + ->scalarNode('strategy')->defaultValue(AccessDecisionManager::STRATEGY_AFFIRMATIVE)->end() ->booleanNode('allow_if_all_abstain')->defaultFalse()->end() ->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end() ->end() @@ -168,6 +169,7 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode) ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() ->prototype('scalar')->end() ->end() + ->scalarNode('allow_if')->defaultNull()->end() ->end() ->fixXmlConfig('role') ->children() @@ -198,6 +200,11 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto $firewallNodeBuilder ->scalarNode('pattern')->end() + ->scalarNode('host')->end() + ->arrayNode('methods') + ->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->prototype('scalar')->end() + ->end() ->booleanNode('security')->defaultTrue()->end() ->scalarNode('request_matcher')->end() ->scalarNode('access_denied_url')->end() @@ -209,10 +216,36 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->arrayNode('logout') ->treatTrueLike(array()) ->canBeUnset() + ->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) { + $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) { + $v['csrf_token_id'] = $v['intention']; + unset($v['intention']); + + return $v; + }) + ->end() ->children() ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end() - ->scalarNode('csrf_provider')->cannotBeEmpty()->end() - ->scalarNode('intention')->defaultValue('logout')->end() + ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end() + ->scalarNode('csrf_token_id')->defaultValue('logout')->end() ->scalarNode('path')->defaultValue('/logout')->end() ->scalarNode('target')->defaultValue('/')->end() ->scalarNode('success_handler')->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index cef118de77d80..5f75372c319bc 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -173,7 +173,7 @@ protected function createAuthenticationSuccessHandler($container, $id, $config) return $config['success_handler']; } - $successHandlerId = 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); + $successHandlerId = $this->getSuccessHandlerId($id); $successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.success_handler')); $successHandler->replaceArgument(1, array_intersect_key($config, $this->defaultSuccessHandlerOptions)); @@ -188,11 +188,21 @@ protected function createAuthenticationFailureHandler($container, $id, $config) return $config['failure_handler']; } - $id = 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); + $id = $this->getFailureHandlerId($id); $failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.failure_handler')); $failureHandler->replaceArgument(2, array_intersect_key($config, $this->defaultFailureHandlerOptions)); return $id; } + + protected function getSuccessHandlerId($id) + { + return 'security.authentication.success_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); + } + + protected function getFailureHandlerId($id) + { + return 'security.authentication.failure_handler.'.$id.'.'.str_replace('-', '_', $this->getKey()); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index ce06bba7d281a..b674c47e15bf0 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -75,12 +75,10 @@ protected function createListener($container, $id, $config, $userProvider) { $listenerId = parent::createListener($container, $id, $config, $userProvider); - if (isset($config['csrf_provider'])) { - $container - ->getDefinition($listenerId) - ->addArgument(new Reference($config['csrf_provider'])) - ; - } + $container + ->getDefinition($listenerId) + ->addArgument(isset($config['csrf_provider']) ? new Reference($config['csrf_provider']) : null) + ; return $listenerId; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimpleFormFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimpleFormFactory.php new file mode 100644 index 0000000000000..9da6601ff547b --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimpleFormFactory.php @@ -0,0 +1,93 @@ + + * + * 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; + +/** + * @author Jordi Boggiano + */ +class SimpleFormFactory extends FormLoginFactory +{ + public function __construct() + { + parent::__construct(); + + $this->addOption('authenticator', null); + } + + public function getKey() + { + return 'simple-form'; + } + + public function addConfiguration(NodeDefinition $node) + { + parent::addConfiguration($node); + + $node->children() + ->scalarNode('authenticator')->cannotBeEmpty()->end() + ->end(); + } + + protected function getListenerId() + { + return 'security.authentication.listener.simple_form'; + } + + protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) + { + $provider = 'security.authentication.provider.simple_form.'.$id; + $container + ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.simple')) + ->replaceArgument(0, new Reference($config['authenticator'])) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(2, $id) + ; + + return $provider; + } + + protected function createListener($container, $id, $config, $userProvider) + { + $listenerId = parent::createListener($container, $id, $config, $userProvider); + + $simpleAuthHandlerId = 'security.authentication.simple_success_failure_handler.'.$id; + $simpleAuthHandler = $container->setDefinition($simpleAuthHandlerId, new DefinitionDecorator('security.authentication.simple_success_failure_handler')); + $simpleAuthHandler->replaceArgument(0, new Reference($config['authenticator'])); + $simpleAuthHandler->replaceArgument(1, new Reference($this->getSuccessHandlerId($id))); + $simpleAuthHandler->replaceArgument(2, new Reference($this->getFailureHandlerId($id))); + + $listener = $container->getDefinition($listenerId); + $listener->replaceArgument(5, new Reference($simpleAuthHandlerId)); + $listener->replaceArgument(6, new Reference($simpleAuthHandlerId)); + $listener->addArgument(new Reference($config['authenticator'])); + + return $listenerId; + } + + protected function createEntryPoint($container, $id, $config, $defaultEntryPoint) + { + $entryPointId = 'security.authentication.form_entry_point.'.$id; + $container + ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point')) + ->addArgument(new Reference('security.http_utils')) + ->addArgument($config['login_path']) + ->addArgument($config['use_forward']) + ; + + return $entryPointId; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php new file mode 100644 index 0000000000000..27d8c5f050ec5 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimplePreAuthenticationFactory.php @@ -0,0 +1,62 @@ + + * + * 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; + +/** + * @author Jordi Boggiano + */ +class SimplePreAuthenticationFactory implements SecurityFactoryInterface +{ + public function getPosition() + { + return 'pre_auth'; + } + + public function getKey() + { + return 'simple-preauth'; + } + + public function addConfiguration(NodeDefinition $node) + { + $node + ->children() + ->scalarNode('provider')->end() + ->scalarNode('authenticator')->cannotBeEmpty()->end() + ->end() + ; + } + + public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) + { + $provider = 'security.authentication.provider.simple_preauth.'.$id; + $container + ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.simple')) + ->replaceArgument(0, new Reference($config['authenticator'])) + ->replaceArgument(1, new Reference($userProvider)) + ->replaceArgument(2, $id) + ; + + // listener + $listenerId = 'security.authentication.listener.simple_preauth.'.$id; + $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.simple_preauth')); + $listener->replaceArgument(2, $id); + $listener->replaceArgument(3, new Reference($config['authenticator'])); + + return array($provider, $listenerId, null); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 23ad0a8700f54..78a10c822395d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; /** * SecurityExtension. @@ -32,10 +33,12 @@ class SecurityExtension extends Extension { private $requestMatchers = array(); + private $expressions = array(); private $contextListeners = array(); private $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me'); private $factories = array(); private $userProviderFactories = array(); + private $expressionLanguage; public function __construct() { @@ -63,6 +66,11 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('templating_twig.xml'); $loader->load('collectors.xml'); + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + $container->removeDefinition('security.expression_language'); + $container->removeDefinition('security.access.expression_voter'); + } + // set some global scalars $container->setParameter('security.access.denied_url', $config['access_denied_url']); $container->setParameter('security.authentication.manager.erase_credentials', $config['erase_credentials']); @@ -187,8 +195,13 @@ private function createAuthorization($config, ContainerBuilder $container) $access['ips'] ); + $attributes = $access['roles']; + if ($access['allow_if']) { + $attributes[] = $this->createExpression($container, $access['allow_if']); + } + $container->getDefinition('security.access_map') - ->addMethodCall('add', array($matcher, $access['roles'], $access['requires_channel'])); + ->addMethodCall('add', array($matcher, $attributes, $access['requires_channel'])); } } @@ -243,8 +256,11 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $matcher = null; if (isset($firewall['request_matcher'])) { $matcher = new Reference($firewall['request_matcher']); - } elseif (isset($firewall['pattern'])) { - $matcher = $this->createRequestMatcher($container, $firewall['pattern']); + } elseif (isset($firewall['pattern']) || isset($firewall['host'])) { + $pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null; + $host = isset($firewall['host']) ? $firewall['host'] : null; + $methods = isset($firewall['methods']) ? $firewall['methods'] : array(); + $matcher = $this->createRequestMatcher($container, $pattern, $host, $methods); } // Security disabled? @@ -281,7 +297,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.logout_listener')); $listener->replaceArgument(3, array( 'csrf_parameter' => $firewall['logout']['csrf_parameter'], - 'intention' => $firewall['logout']['intention'], + 'intention' => $firewall['logout']['csrf_token_id'], 'logout_path' => $firewall['logout']['path'], )); $listeners[] = new Reference($listenerId); @@ -297,8 +313,8 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $listener->replaceArgument(2, new Reference($logoutSuccessHandlerId)); // add CSRF provider - if (isset($firewall['logout']['csrf_provider'])) { - $listener->addArgument(new Reference($firewall['logout']['csrf_provider'])); + if (isset($firewall['logout']['csrf_token_generator'])) { + $listener->addArgument(new Reference($firewall['logout']['csrf_token_generator'])); } // add session logout handler @@ -326,9 +342,9 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a ->addMethodCall('registerListener', array( $id, $firewall['logout']['path'], - $firewall['logout']['intention'], + $firewall['logout']['csrf_token_id'], $firewall['logout']['csrf_parameter'], - isset($firewall['logout']['csrf_provider']) ? new Reference($firewall['logout']['csrf_provider']) : null, + isset($firewall['logout']['csrf_token_generator']) ? new Reference($firewall['logout']['csrf_token_generator']) : null, )) ; } @@ -566,6 +582,22 @@ private function createSwitchUserListener($container, $id, $config, $defaultProv return $switchUserListenerId; } + private function createExpression($container, $expression) + { + if (isset($this->expressions[$id = 'security.expression.'.sha1($expression)])) { + return $this->expressions[$id]; + } + + $container + ->register($id, 'Symfony\Component\ExpressionLanguage\SerializedParsedExpression') + ->setPublic(false) + ->addArgument($expression) + ->addArgument(serialize($this->getExpressionLanguage()->parse($expression, array('token', 'user', 'object', 'roles', 'request', 'trust_resolver'))->getNodes())) + ; + + return $this->expressions[$id] = new Reference($id); + } + private function createRequestMatcher($container, $path = null, $host = null, $methods = array(), $ip = null, array $attributes = array()) { $serialized = serialize(array($path, $host, $methods, $ip, $attributes)); @@ -624,4 +656,16 @@ public function getConfiguration(array $config, ContainerBuilder $container) // first assemble the factories return new MainConfiguration($this->factories, $this->userProviderFactories); } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(); + } + + return $this->expressionLanguage; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/AclSchemaListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/AclSchemaListener.php index 53758abb761b3..8faa9ac366fd6 100644 --- a/src/Symfony/Bundle/SecurityBundle/EventListener/AclSchemaListener.php +++ b/src/Symfony/Bundle/SecurityBundle/EventListener/AclSchemaListener.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\EventListener; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Security\Acl\Dbal\Schema; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; /** @@ -21,16 +21,16 @@ */ class AclSchemaListener { - private $container; + private $schema; - public function __construct(ContainerInterface $container) + public function __construct(Schema $schema) { - $this->container = $container; + $this->schema = $schema; } public function postGenerateSchema(GenerateSchemaEventArgs $args) { $schema = $args->getSchema(); - $this->container->get('security.acl.dbal.schema')->addToSchema($schema); + $this->schema->addToSchema($schema); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml index f6106f7f70513..84f836c6e74a0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml @@ -10,7 +10,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index dd2a7fc30d116..d90f3206dba07 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -32,17 +32,21 @@ Symfony\Component\Security\Core\Authorization\Voter\RoleVoter Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter + Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter Symfony\Component\Security\Http\Firewall Symfony\Bundle\SecurityBundle\Security\FirewallMap Symfony\Bundle\SecurityBundle\Security\FirewallContext Symfony\Component\HttpFoundation\RequestMatcher + Symfony\Component\HttpFoundation\ExpressionRequestMatcher Symfony\Component\Security\Core\Role\RoleHierarchy Symfony\Component\Security\Http\HttpUtils Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator + + Symfony\Component\Security\Core\Authorization\ExpressionLanguage @@ -78,6 +82,7 @@ + @@ -104,6 +109,13 @@ + + + + + + + @@ -139,12 +151,5 @@ - - - - - %kernel.cache_dir%/secure_random.seed - - diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_acl_dbal.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_acl_dbal.xml index aac84a3ce5311..a61d648b0b709 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_acl_dbal.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_acl_dbal.xml @@ -37,7 +37,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index 7144f562f329b..a8d9bf316c8db 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -12,6 +12,10 @@ Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener + Symfony\Component\Security\Http\Firewall\SimpleFormAuthenticationListener + + Symfony\Component\Security\Http\Firewall\SimplePreAuthenticationListener + Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint @@ -35,12 +39,14 @@ Symfony\Component\Security\Http\Firewall\ContextListener Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider + Symfony\Component\Security\Core\Authentication\Provider\SimpleAuthenticationProvider Symfony\Component\Security\Core\Authentication\Provider\PreAuthenticatedAuthenticationProvider Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler + Symfony\Component\Security\Http\Authentication\SimpleAuthenticationHandler @@ -133,6 +139,29 @@ abstract="true"> + + + + + + + + + + + + + + + + + + + + @@ -170,6 +199,12 @@ %security.authentication.hide_user_not_found% + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 7d810fde389f9..5de413658632e 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -19,6 +19,8 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimplePreAuthenticationFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory; /** @@ -38,6 +40,8 @@ public function build(ContainerBuilder $container) $extension->addSecurityListenerFactory(new HttpDigestFactory()); $extension->addSecurityListenerFactory(new RememberMeFactory()); $extension->addSecurityListenerFactory(new X509Factory()); + $extension->addSecurityListenerFactory(new SimplePreAuthenticationFactory()); + $extension->addSecurityListenerFactory(new SimpleFormFactory()); $extension->addUserProviderFactory(new InMemoryFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); diff --git a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php index 1fef75ca09c22..3532c2cb780bf 100644 --- a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php +++ b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php @@ -12,8 +12,10 @@ namespace Symfony\Bundle\SecurityBundle\Templating\Helper; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Templating\Helper\Helper; /** @@ -24,7 +26,7 @@ class LogoutUrlHelper extends Helper { private $container; - private $listeners; + private $listeners = array(); private $router; /** @@ -37,21 +39,26 @@ public function __construct(ContainerInterface $container, UrlGeneratorInterface { $this->container = $container; $this->router = $router; - $this->listeners = array(); } /** * Registers a firewall's LogoutListener, allowing its URL to be generated. * - * @param string $key The firewall key - * @param string $logoutPath The path that starts the logout process - * @param string $intention The intention for CSRF token generation - * @param string $csrfParameter The CSRF token parameter name - * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance + * @param string $key The firewall key + * @param string $logoutPath The path that starts the logout process + * @param string $csrfTokenId The ID of the CSRF token + * @param string $csrfParameter The CSRF token parameter name + * @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance */ - public function registerListener($key, $logoutPath, $intention, $csrfParameter, CsrfProviderInterface $csrfProvider = null) + public function registerListener($key, $logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager = null) { - $this->listeners[$key] = array($logoutPath, $intention, $csrfParameter, $csrfProvider); + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new \InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); + } + + $this->listeners[$key] = array($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager); } /** @@ -94,12 +101,12 @@ private function generateLogoutUrl($key, $referenceType) throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key)); } - list($logoutPath, $intention, $csrfParameter, $csrfProvider) = $this->listeners[$key]; + list($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager) = $this->listeners[$key]; - $parameters = null !== $csrfProvider ? array($csrfParameter => $csrfProvider->generateCsrfToken($intention)) : array(); + $parameters = null !== $csrfTokenManager ? array($csrfParameter => (string) $csrfTokenManager->getToken($csrfTokenId)) : array(); if ('/' === $logoutPath[0]) { - $request = $this->container->get('request'); + $request = $this->container->get('request_stack')->getCurrentRequest(); $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBasePath().$logoutPath; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 86ddb828f5799..ae24b11f11c48 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -84,9 +84,42 @@ public function testFirewalls() 'security.authentication.switchuser_listener.secure', 'security.access_listener', ), + array( + 'security.channel_listener', + 'security.context_listener.0', + 'security.authentication.listener.basic.host', + 'security.authentication.listener.anonymous.host', + 'security.access_listener', + ), ), $listeners); } + public function testFirewallRequestMatchers() + { + $container = $this->getContainer('container1'); + + $arguments = $container->getDefinition('security.firewall.map')->getArguments(); + $matchers = array(); + + foreach ($arguments[1] as $reference) { + if ($reference instanceof Reference) { + $definition = $container->getDefinition((string) $reference); + $matchers[] = $definition->getArguments(); + } + } + + $this->assertEquals(array( + array( + '/login', + ), + array( + '/test', + 'foo\\.example\\.org', + array('GET', 'POST'), + ), + ), $matchers); + } + public function testAccess() { $container = $this->getContainer('container1'); @@ -100,7 +133,7 @@ public function testAccess() $matcherIds = array(); foreach ($rules as $rule) { - list($matcherId, $roles, $channel) = $rule; + list($matcherId, $attributes, $channel) = $rule; $requestMatcher = $container->getDefinition($matcherId); $this->assertFalse(isset($matcherIds[$matcherId])); @@ -108,19 +141,23 @@ public function testAccess() $i = count($matcherIds); if (1 === $i) { - $this->assertEquals(array('ROLE_USER'), $roles); + $this->assertEquals(array('ROLE_USER'), $attributes); $this->assertEquals('https', $channel); $this->assertEquals( array('/blog/524', null, array('GET', 'POST')), $requestMatcher->getArguments() ); } elseif (2 === $i) { - $this->assertEquals(array('IS_AUTHENTICATED_ANONYMOUSLY'), $roles); + $this->assertEquals(array('IS_AUTHENTICATED_ANONYMOUSLY'), $attributes); $this->assertNull($channel); $this->assertEquals( array('/blog/.*'), $requestMatcher->getArguments() ); + } elseif (3 === $i) { + $this->assertEquals('IS_AUTHENTICATED_ANONYMOUSLY', $attributes[0]); + $expression = $container->getDefinition($attributes[1])->getArgument(0); + $this->assertEquals("token.getUsername() matches '/^admin/'", $expression); } } } 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 bb79da3fb05a6..9a15ea5fd7c3e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -71,11 +71,19 @@ 'x509' => true, 'logout' => true, ), + 'host' => array( + 'pattern' => '/test', + 'host' => 'foo\\.example\\.org', + 'methods' => array('GET', 'POST'), + 'anonymous' => true, + 'http_basic' => true, + ), ), 'access_control' => array( array('path' => '/blog/524', 'role' => 'ROLE_USER', 'requires_channel' => 'https', 'methods' => array('get', 'POST')), array('path' => '/blog/.*', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'), + array('path' => '/blog/524', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'allow_if' => "token.getUsername() matches '/^admin/'"), ), 'role_hierarchy' => array( 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 cb452e9316693..2368aa312da99 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -57,11 +57,17 @@ + + + + + ROLE_USER ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH ROLE_USER,ROLE_ADMIN + 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 169f7fa431261..9bfe29230bbdb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -53,6 +53,12 @@ security: switch_user: true x509: true logout: true + host: + pattern: /test + host: foo\.example\.org + methods: [GET,POST] + anonymous: true + http_basic: true role_hierarchy: ROLE_ADMIN: ROLE_USER @@ -64,3 +70,4 @@ security: - path: /blog/.* role: IS_AUTHENTICATED_ANONYMOUSLY + - { path: /blog/524, role: IS_AUTHENTICATED_ANONYMOUSLY, allow_if: "token.getUsername() matches '/^admin/'" } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index 047821cfdb378..402b321968739 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -67,4 +67,49 @@ public function testManyConfigForProvider() $configuration = new MainConfiguration(array(), array()); $config = $processor->processConfiguration($configuration, array($config)); } + + public function testCsrfAliases() + { + $config = array( + 'firewalls' => array( + 'stub' => array( + 'logout' => array( + 'csrf_provider' => 'a_token_generator', + 'intention' => 'a_token_id', + ), + ), + ), + ); + $config = array_merge(static::$minimalConfig, $config); + + $processor = new Processor(); + $configuration = new MainConfiguration(array(), array()); + $processedConfig = $processor->processConfiguration($configuration, array($config)); + $this->assertTrue(isset($processedConfig['firewalls']['stub']['logout']['csrf_token_generator'])); + $this->assertEquals('a_token_generator', $processedConfig['firewalls']['stub']['logout']['csrf_token_generator']); + $this->assertTrue(isset($processedConfig['firewalls']['stub']['logout']['csrf_token_id'])); + $this->assertEquals('a_token_id', $processedConfig['firewalls']['stub']['logout']['csrf_token_id']); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCsrfOriginalAndAliasValueCausesException() + { + $config = array( + 'firewalls' => array( + 'stub' => array( + 'logout' => array( + 'csrf_token_id' => 'a_token_id', + 'intention' => 'old_name', + ), + ), + ), + ); + $config = array_merge(static::$minimalConfig, $config); + + $processor = new Processor(); + $configuration = new MainConfiguration(array(), array()); + $processedConfig = $processor->processConfiguration($configuration, array($config)); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 52db39200129c..4dd33020c572d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -111,9 +111,9 @@ protected function getRawContainer() protected function getContainer() { - $containter = $this->getRawContainer(); - $containter->compile(); + $container = $this->getRawContainer(); + $container->compile(); - return $containter; + return $container; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php index 9a33781a7ed36..271322481f2d2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php @@ -12,23 +12,24 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Controller; use Symfony\Component\Security\Core\SecurityContext; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\DependencyInjection\ContainerAware; class LocalizedController extends ContainerAware { - public function loginAction() + public function loginAction(Request $request) { // get the login error if there is one - if ($this->container->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { - $error = $this->container->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR); } else { - $error = $this->container->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityContext::AUTHENTICATION_ERROR); } return $this->container->get('templating')->renderResponse('FormLoginBundle:Localized:login.html.twig', array( // last username entered by the user - 'last_username' => $this->container->get('request')->getSession()->get(SecurityContext::LAST_USERNAME), + 'last_username' => $request->getSession()->get(SecurityContext::LAST_USERNAME), 'error' => $error, )); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php index eabb5683e6672..e8ad74a2a696b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php @@ -12,24 +12,25 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Controller; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\SecurityContext; use Symfony\Component\DependencyInjection\ContainerAware; class LoginController extends ContainerAware { - public function loginAction() + public function loginAction(Request $request) { // get the login error if there is one - if ($this->container->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { - $error = $this->container->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR); } else { - $error = $this->container->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityContext::AUTHENTICATION_ERROR); } return $this->container->get('templating')->renderResponse('FormLoginBundle:Login:login.html.twig', array( // last username entered by the user - 'last_username' => $this->container->get('request')->getSession()->get(SecurityContext::LAST_USERNAME), + 'last_username' => $request->getSession()->get(SecurityContext::LAST_USERNAME), 'error' => $error, )); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/config/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/config/routing.yml index 535df3576c2fb..6992f80a0a124 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/config/routing.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/config/routing.yml @@ -37,3 +37,6 @@ form_logout: form_secure_action: path: /secure-but-not-covered-by-access-control defaults: { _controller: FormLoginBundle:Login:secure } + +protected-via-expression: + path: /protected-via-expression diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php index 5ec56deb3677e..f86ec326afa55 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php @@ -91,6 +91,28 @@ public function testSecurityConfigurationForMultipleIPAddresses($config) $this->assertRestricted($barredClient, '/secured-by-two-ips'); } + /** + * @dataProvider getConfigs + */ + public function testSecurityConfigurationForExpression($config) + { + $allowedClient = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array('HTTP_USER_AGENT' => 'Firefox 1.0')); + $this->assertAllowed($allowedClient, '/protected-via-expression'); + + $barredClient = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array()); + $this->assertRestricted($barredClient, '/protected-via-expression'); + + $allowedClient = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array()); + + $allowedClient->request('GET', '/protected-via-expression'); + $form = $allowedClient->followRedirect()->selectButton('login')->form(); + $form['_username'] = 'johannes'; + $form['_password'] = 'test'; + $allowedClient->submit($form); + $this->assertRedirect($allowedClient->getResponse(), '/protected-via-expression'); + $this->assertAllowed($allowedClient, '/protected-via-expression'); + } + private function assertAllowed($client, $path) { $client->request('GET', $path); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php index e1c96d32e4b31..731c32073c949 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php @@ -23,15 +23,6 @@ public static function assertRedirect($response, $location) self::assertEquals('http://localhost'.$location, $response->headers->get('Location')); } - protected function setUp() - { - if (!class_exists('Twig_Environment')) { - $this->markTestSkipped('Twig is not available.'); - } - - parent::setUp(); - } - protected function deleteTmpDir($testCase) { if (!file_exists($dir = sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$testCase)) { 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 e0347e1dc4e00..e1e2b0e883933 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -37,12 +37,12 @@ security: username_parameter: "user_login[username]" password_parameter: "user_login[password]" csrf_parameter: "user_login[_token]" - csrf_provider: form.csrf_provider + csrf_provider: security.csrf.token_manager anonymous: ~ logout: path: /logout_path target: / - csrf_provider: form.csrf_provider + csrf_provider: security.csrf.token_manager access_control: - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml index 58bd9f2d8af3c..624637b0c82aa 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml @@ -31,4 +31,5 @@ security: - { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/secured-by-two-ips$, ips: [1.1.1.1, 2.2.2.2], roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/highly_protected_resource$, roles: IS_ADMIN } + - { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or has_role('ROLE_USER')" } - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 0b7e49d9ad67a..d681c36c8cbda 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -17,23 +17,23 @@ ], "require": { "php": ">=5.3.3", - "symfony/security": "~2.2", + "symfony/security": "~2.5", "symfony/http-kernel": "~2.2" }, "require-dev": { - "symfony/browser-kit": "~2.3", + "symfony/browser-kit": "~2.4", "symfony/css-selector": "~2.0,>=2.0.5", "symfony/dependency-injection": "~2.3", "symfony/dom-crawler": "~2.0,>=2.0.5", - "symfony/form": "~2.3", - "symfony/framework-bundle": "~2.2,<2.6.0", + "symfony/form": "~2.4", + "symfony/framework-bundle": "~2.4,<2.6.0", "symfony/http-foundation": "~2.3", "symfony/twig-bundle": "~2.2", "symfony/twig-bridge": "~2.2,>=2.2.6", - "symfony/form": "~2.3", "symfony/process": "~2.0,>=2.0.5", "symfony/validator": "~2.2", "symfony/yaml": "~2.0,>=2.0.5", + "symfony/expression-language": "~2.4", "twig/twig": "~1.12" }, "autoload": { @@ -43,7 +43,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php b/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php new file mode 100644 index 0000000000000..c659592ede271 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Lists twig functions, filters, globals and tests present in the current project + * + * @author Jordi Boggiano + */ +class DebugCommand extends ContainerAwareCommand +{ + protected function configure() + { + $this + ->setName('twig:debug') + ->setDefinition(array( + new InputArgument('filter', InputArgument::OPTIONAL, 'Show details for all entries matching this filter'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'Output format: text or json', 'text'), + )) + ->setDescription('Shows a list of twig functions, filters, globals and tests') + ->setHelp(<<%command.name% command outputs a list of twig functions, +filters, globals and tests. Output can be filtered with an optional argument. + +php %command.full_name% + +The command lists all functions, filters, etc. + +php %command.full_name% date + +The command lists everything that contains the word date. + +php %command.full_name% --format=json + +The command lists everything in a machine readable json format. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $twig = $this->getContainer()->get('twig'); + $types = array('functions', 'filters', 'tests', 'globals'); + + if ($input->getOption('format') === 'json') { + $data = array(); + foreach ($types as $type) { + foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) { + $data[$type][$name] = $this->getMetadata($type, $entity); + } + } + $data['tests'] = array_keys($data['tests']); + $output->writeln(json_encode($data)); + + return 0; + } + + $filter = $input->getArgument('filter'); + + foreach ($types as $index => $type) { + $items = array(); + foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) { + if (!$filter || false !== strpos($name, $filter)) { + $items[$name] = $name.$this->getPrettyMetadata($type, $entity); + } + } + + if (!$items) { + continue; + } + if ($index > 0) { + $output->writeln(''); + } + $output->writeln(''.ucfirst($type).''); + ksort($items); + foreach ($items as $item) { + $output->writeln(' '.$item); + } + } + + return 0; + } + + private function getMetadata($type, $entity) + { + if ($type === 'globals') { + return $entity; + } + if ($type === 'tests') { + return; + } + if ($type === 'functions' || $type === 'filters') { + $args = array(); + $cb = $entity->getCallable(); + if (is_null($cb)) { + return; + } + if (is_array($cb)) { + if (!method_exists($cb[0], $cb[1])) { + return; + } + $refl = new \ReflectionMethod($cb[0], $cb[1]); + } elseif (is_object($cb) && is_callable($cb)) { + $refl = new \ReflectionMethod($cb, '__invoke'); + } elseif (function_exists($cb)) { + $refl = new \ReflectionFunction($cb); + } elseif (is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) { + $refl = new \ReflectionMethod($m[1], $m[2]); + } else { + throw new \UnexpectedValueException('Unsupported callback type'); + } + + // filter out context/environment args + $args = array_filter($refl->getParameters(), function ($param) use ($entity) { + if ($entity->needsContext() && $param->getName() === 'context') { + return false; + } + + return !$param->getClass() || $param->getClass()->getName() !== 'Twig_Environment'; + }); + + // format args + $args = array_map(function ($param) { + if ($param->isDefaultValueAvailable()) { + return $param->getName().' = '.json_encode($param->getDefaultValue()); + } + + return $param->getName(); + }, $args); + + if ($type === 'filters') { + // remove the value the filter is applied on + array_shift($args); + } + + return $args; + } + } + + private function getPrettyMetadata($type, $entity) + { + if ($type === 'tests') { + return ''; + } + + try { + $meta = $this->getMetadata($type, $entity); + if ($meta === null) { + return '(unknown?)'; + } + } catch (\UnexpectedValueException $e) { + return ' '.$e->getMessage().''; + } + + if ($type === 'globals') { + if (is_object($meta)) { + return ' = object('.get_class($meta).')'; + } + + return ' = '.substr(@json_encode($meta), 0, 50); + } + + if ($type === 'functions') { + return '('.implode(', ', $meta).')'; + } + + if ($type === 'filters') { + return $meta ? '('.implode(', ', $meta).')' : ''; + } + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php index aa01ce6e7b0aa..95c892cb0433b 100644 --- a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php +++ b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php @@ -11,141 +11,68 @@ namespace Symfony\Bundle\TwigBundle\Command; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Bridge\Twig\Command\LintCommand as BaseLintCommand; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\Finder\Finder; /** * Command that will validate your template syntax and output encountered errors. * * @author Marc Weistroff + * @author Jérôme Tamarelle */ -class LintCommand extends ContainerAwareCommand +class LintCommand extends BaseLintCommand implements ContainerAwareInterface { - protected function configure() + /** + * @var ContainerInterface|null + */ + private $container; + + /** + * {@inheritdoc} + */ + public function setContainer(ContainerInterface $container = null) { - $this - ->setName('twig:lint') - ->setDescription('Lints a template and outputs encountered errors') - ->addArgument('filename') - ->setHelp(<<%command.name% command lints a template and outputs to stdout -the first encountered syntax error. - -php %command.full_name% filename - -The command gets the contents of filename and validates its syntax. - -php %command.full_name% dirname - -The command finds all twig templates in dirname and validates the syntax -of each Twig template. - -php %command.full_name% @AcmeMyBundle - -The command finds all twig templates in the AcmeMyBundle bundle and validates -the syntax of each Twig template. - -cat filename | php %command.full_name% - -The command gets the template contents from stdin and validates its syntax. -EOF - ) - ; + $this->container = $container; } - protected function execute(InputInterface $input, OutputInterface $output) + /** + * {@inheritdoc} + */ + protected function getTwigEnvironment() { - $twig = $this->getContainer()->get('twig'); - $template = null; - $filename = $input->getArgument('filename'); - - if (!$filename) { - if (0 !== ftell(STDIN)) { - throw new \RuntimeException("Please provide a filename or pipe template content to stdin."); - } - - while (!feof(STDIN)) { - $template .= fread(STDIN, 1024); - } - - return $this->validateTemplate($twig, $output, $template); - } - - if (0 !== strpos($filename, '@') && !is_readable($filename)) { - throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); - } - - $files = array(); - if (is_file($filename)) { - $files = array($filename); - } elseif (is_dir($filename)) { - $files = Finder::create()->files()->in($filename)->name('*.twig'); - } else { - $dir = $this->getApplication()->getKernel()->locateResource($filename); - $files = Finder::create()->files()->in($dir)->name('*.twig'); - } - - $errors = 0; - foreach ($files as $file) { - $errors += $this->validateTemplate($twig, $output, file_get_contents($file), $file); - } - - return $errors > 0 ? 1 : 0; + return $this->container->get('twig'); } - protected function validateTemplate(\Twig_Environment $twig, OutputInterface $output, $template, $file = null) + /** + * {@inheritdoc} + */ + protected function configure() { - try { - $twig->parse($twig->tokenize($template, $file ? (string) $file : null)); - $output->writeln('OK'.($file ? sprintf(' in %s', $file) : '')); - } catch (\Twig_Error $e) { - $this->renderException($output, $template, $e, $file); + parent::configure(); - return 1; - } - - return 0; - } + $this + ->setHelp( + $this->getHelp().<<getTemplateLine(); - $lines = $this->getContext($template, $line); - if ($file) { - $output->writeln(sprintf("KO in %s (line %s)", $file, $line)); - } else { - $output->writeln(sprintf("KO (line %s)", $line)); - } +Or all template files in a bundle: - foreach ($lines as $no => $code) { - $output->writeln(sprintf( - "%s %-6s %s", - $no == $line ? '>>' : ' ', - $no, - $code - )); - if ($no == $line) { - $output->writeln(sprintf('>> %s ', $exception->getRawMessage())); - } - } +php %command.full_name% @AcmeDemoBundle +EOF + ) + ; } - protected function getContext($template, $line, $context = 3) + protected function findFiles($filename) { - $lines = explode("\n", $template); - - $position = max(0, $line - $context); - $max = min(count($lines), $line - 1 + $context); + if (0 === strpos($filename, '@')) { + $dir = $this->getApplication()->getKernel()->locateResource($filename); - $result = array(); - while ($position < $max) { - $result[$position + 1] = $lines[$position]; - $position++; + return Finder::create()->files()->in($dir)->name('*.twig'); } - return $result; + return parent::findFiles($filename); } } diff --git a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index 9f1edad12c8d1..4f6140b134032 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -70,17 +70,13 @@ public function showAction(Request $request, FlattenException $exception, DebugL */ protected function getAndCleanOutputBuffering($startObLevel) { - // ob_get_level() never returns 0 on some Windows configurations, so if - // the level is the same two times in a row, the loop should be stopped. - $previousObLevel = null; - $currentContent = ''; - - while (($obLevel = ob_get_level()) > $startObLevel && $obLevel !== $previousObLevel) { - $previousObLevel = $obLevel; - $currentContent .= ob_get_clean(); + if (ob_get_level() <= $startObLevel) { + return ''; } - return $currentContent; + Response::closeOutputBuffers($startObLevel + 1, true); + + return ob_get_clean(); } /** diff --git a/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php b/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php index 20834d7fa2079..d12124a345cfd 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php +++ b/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\TwigBundle\Extension; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Routing\RequestContext; /** * Twig extension for Symfony assets helper @@ -21,10 +22,12 @@ class AssetsExtension extends \Twig_Extension { private $container; + private $context; - public function __construct(ContainerInterface $container) + public function __construct(ContainerInterface $container, RequestContext $requestContext = null) { $this->container = $container; + $this->context = $requestContext; } /** @@ -45,14 +48,22 @@ public function getFunctions() * * Absolute paths (i.e. http://...) are returned unmodified. * - * @param string $path A public path - * @param string $packageName The name of the asset package to use + * @param string $path A public path + * @param string $packageName The name of the asset package to use + * @param bool $absolute Whether to return an absolute URL or a relative one + * @param string|bool|null $version A specific version * * @return string A public path which takes into account the base path and URL path */ - public function getAssetUrl($path, $packageName = null) + public function getAssetUrl($path, $packageName = null, $absolute = false, $version = null) { - return $this->container->get('templating.helper.assets')->getUrl($path, $packageName); + $url = $this->container->get('templating.helper.assets')->getUrl($path, $packageName, $version); + + if (!$absolute) { + return $url; + } + + return $this->ensureUrlIsAbsolute($url); } /** @@ -76,4 +87,39 @@ public function getName() { return 'assets'; } + + /** + * Ensures an URL is absolute, if possible. + * + * @param string $url The URL that has to be absolute + * + * @throws \RuntimeException + * + * @return string The absolute URL + */ + private function ensureUrlIsAbsolute($url) + { + if (false !== strpos($url, '://') || 0 === strpos($url, '//')) { + return $url; + } + + if (!$this->context) { + throw new \RuntimeException('To generate an absolute URL for an asset, the Symfony Routing component is required.'); + } + + if ('' === $host = $this->context->getHost()) { + return $url; + } + + $scheme = $this->context->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + return $scheme.'://'.$host.$port.$url; + } } diff --git a/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php b/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php index fe0fbe15f3248..3c7f925d034d3 100644 --- a/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php +++ b/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php @@ -11,8 +11,8 @@ namespace Symfony\Bundle\TwigBundle\Loader; -use Symfony\Component\Templating\TemplateNameParserInterface; use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Templating\TemplateNameParserInterface; use Symfony\Component\Templating\TemplateReferenceInterface; /** @@ -38,7 +38,6 @@ public function __construct(FileLocatorInterface $locator, TemplateNameParserInt $this->locator = $locator; $this->parser = $parser; - $this->cache = array(); } /** diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 971f4f17ebefd..a63f824c79812 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -18,6 +18,8 @@ Symfony\Bridge\Twig\Extension\YamlExtension Symfony\Bridge\Twig\Extension\FormExtension Symfony\Bridge\Twig\Extension\HttpKernelExtension + Symfony\Bridge\Twig\Extension\StopwatchExtension + Symfony\Bridge\Twig\Extension\ExpressionExtension Symfony\Bridge\Twig\Form\TwigRendererEngine Symfony\Bridge\Twig\Form\TwigRenderer Symfony\Bridge\Twig\Translation\TwigExtractor @@ -64,6 +66,7 @@ + @@ -86,6 +89,15 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/layout.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/layout.html.twig index c63a7e145d162..75cf10904859b 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/layout.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/layout.html.twig @@ -4,8 +4,8 @@ Codestin Search App - - + + {% block head %}{% endblock %} diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Extension/AssetsExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Extension/AssetsExtensionTest.php new file mode 100644 index 0000000000000..eddf7adcc0694 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/Extension/AssetsExtensionTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Tests\Extension; + +use Symfony\Bundle\TwigBundle\Extension\AssetsExtension; +use Symfony\Bundle\TwigBundle\Tests\TestCase; +use Symfony\Component\Routing\RequestContext; + +class AssetsExtensionTest extends TestCase +{ + /** + * @dataProvider provideGetGetAssetUrlArguments + */ + public function testGetAssetUrl($path, $packageName, $absolute, $relativeUrl, $expectedUrl, $scheme, $host, $httpPort, $httpsPort) + { + $helper = $this->createHelperMock($path, $packageName, $relativeUrl); + $container = $this->createContainerMock($helper); + + $context = $this->createRequestContextMock($scheme, $host, $httpPort, $httpsPort); + + $extension = new AssetsExtension($container, $context); + $this->assertEquals($expectedUrl, $extension->getAssetUrl($path, $packageName, $absolute)); + } + + public function testGetAssetWithoutHost() + { + $path = '/path/to/asset'; + $packageName = null; + $relativeUrl = '/bundle-name/path/to/asset'; + + $helper = $this->createHelperMock($path, $packageName, $relativeUrl); + $container = $this->createContainerMock($helper); + + $context = $this->createRequestContextMock('http', '', 80, 443); + + $extension = new AssetsExtension($container, $context); + $this->assertEquals($relativeUrl, $extension->getAssetUrl($path, $packageName, true)); + } + + public function provideGetGetAssetUrlArguments() + { + return array( + array('/path/to/asset', 'package-name', false, '/bundle-name/path/to/asset', '/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', 'package-name', false, 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', null, false, '/bundle-name/path/to/asset', '/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', 'package-name', true, '/bundle-name/path/to/asset', 'http://symfony.com/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', 'package-name', true, 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', null, true, '/bundle-name/path/to/asset', 'https://symfony.com:92/bundle-name/path/to/asset', 'https', 'symfony.com', null, 92), + array('/path/to/asset', null, true, '/bundle-name/path/to/asset', 'http://symfony.com:660/bundle-name/path/to/asset', 'http', 'symfony.com', 660, null), + ); + } + + private function createRequestContextMock($scheme, $host, $httpPort, $httpsPort) + { + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext') + ->disableOriginalConstructor() + ->getMock(); + $context->expects($this->any()) + ->method('getScheme') + ->will($this->returnValue($scheme)); + $context->expects($this->any()) + ->method('getHost') + ->will($this->returnValue($host)); + $context->expects($this->any()) + ->method('getHttpPort') + ->will($this->returnValue($httpPort)); + $context->expects($this->any()) + ->method('getHttpsPort') + ->will($this->returnValue($httpsPort)); + + return $context; + } + + private function createContainerMock($helper) + { + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container->expects($this->any()) + ->method('get') + ->with('templating.helper.assets') + ->will($this->returnValue($helper)); + + return $container; + } + + private function createHelperMock($path, $packageName, $returnValue) + { + $helper = $this->getMockBuilder('Symfony\Component\Templating\Helper\CoreAssetsHelper') + ->disableOriginalConstructor() + ->getMock(); + $helper->expects($this->any()) + ->method('getUrl') + ->with($path, $packageName) + ->will($this->returnValue($returnValue)); + + return $helper; + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php index ce2e821f46f19..c6d5e277a2486 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php @@ -11,11 +11,9 @@ namespace Symfony\Bundle\TwigBundle\Tests\Loader; -use Symfony\Bundle\TwigBundle\Tests\TestCase; -use Symfony\Bundle\TwigBundle\Loader\FilesystemLoader; -use Symfony\Component\Config\FileLocatorInterface; use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference; -use Symfony\Component\Templating\TemplateNameParserInterface; +use Symfony\Bundle\TwigBundle\Loader\FilesystemLoader; +use Symfony\Bundle\TwigBundle\Tests\TestCase; class FilesystemLoaderTest extends TestCase { diff --git a/src/Symfony/Bundle/TwigBundle/Tests/TestCase.php b/src/Symfony/Bundle/TwigBundle/Tests/TestCase.php index a3848ef472f3d..0c7af8ae4b859 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/TestCase.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/TestCase.php @@ -13,10 +13,4 @@ class TestCase extends \PHPUnit_Framework_TestCase { - protected function setUp() - { - if (!class_exists('Twig_Environment')) { - $this->markTestSkipped('Twig is not available.'); - } - } } diff --git a/src/Symfony/Bundle/TwigBundle/TwigEngine.php b/src/Symfony/Bundle/TwigBundle/TwigEngine.php index 72ea0c49c9001..5ce5c9da9d320 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigEngine.php +++ b/src/Symfony/Bundle/TwigBundle/TwigEngine.php @@ -66,16 +66,7 @@ public function guessDefaultEscapingStrategy($filename) } /** - * Renders a template. - * - * @param mixed $name A template name - * @param array $parameters An array of parameters to pass to the template - * - * @return string The evaluated template as a string - * - * @throws \InvalidArgumentException if the template does not exist - * @throws \RuntimeException if the template cannot be rendered - * @throws \Twig_Error + * {@inheritdoc} */ public function render($name, array $parameters = array()) { @@ -95,13 +86,9 @@ public function render($name, array $parameters = array()) } /** - * Renders a view and returns a Response. - * - * @param string $view The view name - * @param array $parameters An array of parameters to pass to the view - * @param Response $response A Response instance + * {@inheritdoc} * - * @return Response A Response instance + * @throws \Twig_Error if something went wrong like a thrown exception while rendering the template */ public function renderResponse($view, array $parameters = array(), Response $response = null) { diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index c3afb612cf32b..0cdd44ba4d680 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -17,13 +17,17 @@ ], "require": { "php": ">=5.3.3", - "symfony/twig-bridge": "~2.2", + "symfony/twig-bridge": "~2.5", + "symfony/http-foundation": "~2.5", "symfony/http-kernel": "~2.1" }, "require-dev": { "symfony/stopwatch": "~2.2", "symfony/dependency-injection": "~2.2", + "symfony/expression-language": "~2.4", "symfony/config": "~2.2", + "symfony/routing": "~2.1", + "symfony/templating": "~2.1", "symfony/framework-bundle": "~2.1" }, "autoload": { @@ -33,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.5-dev" } } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php index e4b4cb72a078d..d98042dcf5162 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php @@ -44,18 +44,13 @@ public function load(array $configs, ContainerBuilder $container) $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('profiler.xml'); - $loader->load('toolbar.xml'); - - $container->setParameter('web_profiler.debug_toolbar.intercept_redirects', $config['intercept_redirects']); + $container->setParameter('web_profiler.debug_toolbar.position', $config['position']); - if (!$config['toolbar']) { - $mode = WebDebugToolbarListener::DISABLED; - } else { - $mode = WebDebugToolbarListener::ENABLED; + if ($config['toolbar'] || $config['intercept_redirects']) { + $loader->load('toolbar.xml'); + $container->setParameter('web_profiler.debug_toolbar.intercept_redirects', $config['intercept_redirects']); + $container->setParameter('web_profiler.debug_toolbar.mode', $config['toolbar'] ? WebDebugToolbarListener::ENABLED : WebDebugToolbarListener::DISABLED); } - - $container->setParameter('web_profiler.debug_toolbar.mode', $mode); - $container->setParameter('web_profiler.debug_toolbar.position', $config['position']); } /** diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 1a2663d3fa028..196b3e786f119 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -13,10 +13,10 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; -use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** * WebDebugToolbarListener injects the Web Debug Toolbar. @@ -34,13 +34,15 @@ class WebDebugToolbarListener implements EventSubscriberInterface const ENABLED = 2; protected $twig; + protected $urlGenerator; protected $interceptRedirects; protected $mode; protected $position; - public function __construct(\Twig_Environment $twig, $interceptRedirects = false, $mode = self::ENABLED, $position = 'bottom') + public function __construct(\Twig_Environment $twig, $interceptRedirects = false, $mode = self::ENABLED, $position = 'bottom', UrlGeneratorInterface $urlGenerator = null) { $this->twig = $twig; + $this->urlGenerator = $urlGenerator; $this->interceptRedirects = (bool) $interceptRedirects; $this->mode = (int) $mode; $this->position = $position; @@ -53,13 +55,20 @@ public function isEnabled() public function onKernelResponse(FilterResponseEvent $event) { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { - return; - } - $response = $event->getResponse(); $request = $event->getRequest(); + if ($response->headers->has('X-Debug-Token') && null !== $this->urlGenerator) { + $response->headers->set( + 'X-Debug-Token-Link', + $this->urlGenerator->generate('_profiler', array('token' => $response->headers->get('X-Debug-Token'))) + ); + } + + if (!$event->isMasterRequest()) { + return; + } + // do not capture redirects or modify XML HTTP Requests if ($request->isXmlHttpRequest()) { return; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml index 874ba8b216244..ed7e923f0d05d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml @@ -8,6 +8,7 @@ Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController Symfony\Bundle\WebProfilerBundle\Controller\RouterController Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController + Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension @@ -30,5 +31,9 @@ %kernel.debug% + + + + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.xml index 1372b0a61f10a..091127eaf9532 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/toolbar.xml @@ -15,6 +15,7 @@ %web_profiler.debug_toolbar.intercept_redirects% %web_profiler.debug_toolbar.mode% %web_profiler.debug_toolbar.position% + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig new file mode 100644 index 0000000000000..43e706b0a70a4 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -0,0 +1,659 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% from _self import form_tree_entry, form_tree_details %} + +{% block toolbar %} + {% if collector.data|length %} + {% set icon %} + Forms + {% if collector.data.nb_errors %}{{ collector.data.nb_errors }}{% else %}{{ collector.data.forms|length }}{% endif %} + {% endset %} + + {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %} + {% endif %} +{% endblock %} + +{% block menu %} + + + Forms + {% if collector.data.forms|length %} + {{ collector.data.forms|length }} + {% endif %} + +{% endblock %} + +{% block panel %} + + + {% if collector.data.forms|length %} +
    +
    +

    Forms

    + +
      + {% for formName, formData in collector.data.forms %} + {{ form_tree_entry(formName, formData, true) }} + {% endfor %} +
    +
    + + {% for formName, formData in collector.data.forms %} + {{ form_tree_details(formName, formData, collector.data.forms_by_hash) }} + {% endfor %} +
    + {% else %} +

    No forms were submitted for this request.

    + {% endif %} + + +{% endblock %} + +{% macro form_tree_entry(name, data, expanded) %} +
  • +
    + {% if data.children is not empty %} + + {% else %} +
    + {% endif %} + {{ name|default('(no name)') }} + {% if data.errors is defined and data.errors|length > 0 %} +
    {{ data.errors|length }}
    + {% endif %} +
    + + {% if data.children is not empty %} + + {% endif %} +
  • +{% endmacro %} + +{% macro form_tree_details(name, data, forms_by_hash) %} +
    +

    + {{ name|default('(no name)') }} + {% if data.type_class is defined %} + [{{ data.type }}] + {% endif %} +

    + + {% if data.errors is defined and data.errors|length > 0 %} +
    +

    + + Errors + + +

    + + + + + + + + {% for error in data.errors %} + + + + + + {% endfor %} +
    MessageOriginCause
    {{ error.message }} + {% if error.origin is empty %} + This form. + {% elseif forms_by_hash[error.origin] is not defined %} + Unknown. + {% else %} + {{ forms_by_hash[error.origin].name }} + {% endif %} + + {% if error.cause is empty %} + Unknown. + {% elseif error.cause.root is defined %} + Constraint Violation
    +
    {{ error.cause.root }}{% if error.cause.path is not empty %}{% if error.cause.path|first != '[' %}.{% endif %}{{ error.cause.path }}{% endif %} = {{ error.cause.value }}
    + {% else %} +
    {{ error.cause }}
    + {% endif %} +
    +
    + {% endif %} + + {% if data.default_data is defined %} +

    + + Default Data + + +

    + +
    + + + + + + + + + + + + + +
    Model Format + {% if data.default_data.model is defined %} +
    {{ data.default_data.model }}
    + {% else %} + same as normalized format + {% endif %} +
    Normalized Format
    {{ data.default_data.norm }}
    View Format + {% if data.default_data.view is defined %} +
    {{ data.default_data.view }}
    + {% else %} + same as normalized format + {% endif %} +
    +
    + {% endif %} + + {% if data.submitted_data is defined %} +

    + + Submitted Data + + +

    + +
    + {% if data.submitted_data.norm is defined %} + + + + + + + + + + + + + +
    View Format + {% if data.submitted_data.view is defined %} +
    {{ data.submitted_data.view }}
    + {% else %} + same as normalized format + {% endif %} +
    Normalized Format
    {{ data.submitted_data.norm }}
    Model Format + {% if data.submitted_data.model is defined %} +
    {{ data.submitted_data.model }}
    + {% else %} + same as normalized format + {% endif %} +
    + {% else %} +

    This form was not submitted.

    + {% endif %} +
    + {% endif %} + + {% if data.passed_options is defined %} +

    + + Passed Options + + +

    + +
    + {% if data.passed_options|length %} + + + + + + + {% for option, value in data.passed_options %} + + + + + + {% endfor %} +
    OptionPassed ValueResolved Value
    {{ option }}
    {{ value }}
    + {% if data.resolved_options[option] is sameas(value) %} + same as passed value + {% else %} +
    {{ data.resolved_options[option] }}
    + {% endif %} +
    + {% else %} +

    No options where passed when constructing this form.

    + {% endif %} +
    + {% endif %} + + {% if data.resolved_options is defined %} +

    + + Resolved Options + + +

    + + + {% endif %} + + {% if data.view_vars is defined %} +

    + + View Variables + + +

    + + + {% endif %} +
    + + {% for childName, childData in data.children %} + {{ _self.form_tree_details(childName, childData, forms_by_hash) }} + {% endfor %} +{% endmacro %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index 74dda4418b88b..e2d7d5faaed69 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -3,21 +3,21 @@ {% import _self as logger %} {% block toolbar %} - {% if collector.counterrors or collector.countdeprecations %} + {% if collector.counterrors or collector.countdeprecations or collector.countscreams %} {% set icon %} Logs {% if collector.counterrors %} {% set status_color = "red" %} - {% else %} + {% elseif collector.countdeprecations %} {% set status_color = "yellow" %} {% endif %} - {% set error_count = collector.counterrors + collector.countdeprecations %} - {{ error_count }} + {% set error_count = collector.counterrors + collector.countdeprecations + collector.countscreams %} + {{ error_count }} {% endset %} {% set text %} {% if collector.counterrors %}
    - Exception + Errors {{ collector.counterrors }}
    {% endif %} @@ -27,6 +27,12 @@ {{ collector.countdeprecations }} {% endif %} + {% if collector.countscreams %} +
    + Silenced Errors + {{ collector.countscreams }} +
    + {% endif %} {% endset %} {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %} {% endif %} @@ -36,8 +42,8 @@ Logger Logs - {% if collector.counterrors or collector.countdeprecations %} - {% set error_count = collector.counterrors + collector.countdeprecations %} + {% if collector.counterrors or collector.countdeprecations or collector.countscreams %} + {% set error_count = collector.counterrors + collector.countdeprecations + collector.countscreams %} {{ error_count }} @@ -56,12 +62,21 @@
- +