diff --git a/.travis.yml b/.travis.yml index cb5f0ac5f248a..8a87800c5f3cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,3 @@ before_script: - echo '' > ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini - sh -c 'if [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - COMPOSER_ROOT_VERSION=dev-master composer --prefer-source --dev install - - php src/Symfony/Component/Locale/Resources/data/build-data.php - - export USE_INTL_ICU_DATA_VERSION=1 diff --git a/CHANGELOG-2.2.md b/CHANGELOG-2.2.md index 09f759562470d..2c7a93f0045de 100644 --- a/CHANGELOG-2.2.md +++ b/CHANGELOG-2.2.md @@ -128,7 +128,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * 2.2.1 (2013-04-06) * 751abe1: Doctrine cannot handle bare random non-utf8 strings - * 673fd9b: idAsIndex should be true with a smallint or bigint id field. + * 673fd9b: idAsIndex should be true with a smallint or bigint id field. * 64a1d39: Fixed long multibyte parameter logging in DbalLogger:startQuery * 4cf06c1: Keep the file extension in the temporary copy and test that it exists (closes #7482) * 64ac34d: [Security] fixed wrong interface @@ -150,7 +150,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * 6575df6: [Security] use current request attributes to generate redirect url? * 7216cb0: [Validator] fix showing wrong max file size for upload errors * c423f16: [2.1][TwigBridge] Fixes Issue #7342 in TwigBridge - * 7d87ecd: [FrameworkBundle] fixed cahe:clear command's warmup + * 7d87ecd: [FrameworkBundle] fixed cache:clear command's warmup * 5ad4bd1: [TwigBridge] now enter/leave scope on Twig_Node_Module * fe4cc24: [TwigBridge] fixed fixed scope & trans_default_domain node visitor * fc47589: [BrowserKit] added ability to ignored malformed set-cookie header diff --git a/CHANGELOG-2.3.md b/CHANGELOG-2.3.md new file mode 100644 index 0000000000000..db170b4f9a068 --- /dev/null +++ b/CHANGELOG-2.3.md @@ -0,0 +1,133 @@ +CHANGELOG for 2.3.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 2.3 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.3.0...v2.3.1 + +* 2.3.3 (2013-08-07) + + * c35cc5b: added trusted hosts check + * 6d555bc: Fixed metadata serialization + * cd51d82: [Form] fixed wrong call to setTimeZone() (closes #8644) + * 5c359a8: Fix issue with \DateTimeZone::UTC / 'UTC' for PHP 5.4 + * 85330a6: [Form] Fixed patched forms to be valid even if children are not submitted + * cb5e765: [Form] Fixed: If a form is not present in a request, it is not automatically submitted + * 97cbb19: [Form] Removed the "disabled" attribute from the placeholder option in select fields due to problems with the BlackBerry 10 browser + * c138304: [routing] added ability for apache matcher to handle array values + * 1bd45b3: [FrameworkBundle] fixed regression where the command might have the wrong container if the application is reused several times + * b41cf82: [Validator] fixed StaticMethodLoader trying to invoke methods of abstract classes (closes #8589) + * e5fba3c: [Form] fixes empty file-inputs get treated as extra field + * 3553c71: return 0 if there is no valid data + * 50d0727: [DependencyInjection] fixed regression where setting a service to null did not trigger a re-creation of the service when getting it + * dc1fff0: The ignoreAttributes itself should be ignored, too. + * ae7fa11: [Twig] fixed TwigEngine::exists() method when a template contains a syntax error (closes #8546) + * 28e0709: [Validator] fixed ConstraintViolation:: incorrect when nested + * 890934d: handle Optional and Required constraints from XML or YAML sources correctly + * a2eca45: Fixed #8455: PhpExecutableFinder::find() does not always return the correct binary + * 485d53a: [DependencyInjection] Fix Container::camelize to convert beginning and ending chars + * 2317443: [Security] fixed issue where authentication listeners clear unrelated tokens + * 2ebb783: fix issue #8499 modelChoiceList call getPrimaryKey on a non object + * 242b318: [DependencyInjection] Add exception for service name not dumpable in PHP + * d3eb9b7: [Validator] Fixed groups argument misplace for validateValue method from validator class + +* 2.3.2 (2013-07-17) + + * bb59f40: Reverts JSON_NUMERIC_CHECK + * 9c5f8c6: [Yaml] removed wrong comment removal inside a string block + * 2dc1ee0: [HtppKernel] fixed inline fragment renderer + * 06b69b8: fixed inline fragment renderer + * 91bb757: ProgressHelper shows percentage complete. + * 9d1004b: fix handling of a default 'template' as a string + * 82dbaee: [HttpKernel] fixed the inline renderer when passing objects as attributes (closes #7124) + * 8bb4e4d: [DI] Fixed bug requesting non existing service from dumped frozen container + * 6dbd1e1: [WebProfiler] fix content-type parameter + * a830001: Passed the config when building the Configuration in ConfigurableExtension + * c875d0a: [Form] fixed INF usage which does not work on Solaris (closes #8246) + * ab1439e: [Console] Fixed the table rendering with multi-byte strings. + * c0da3ae: [Process] Disable exception on stream_select timeout + * 77f2aa8: [HttpFoundation] fixed issue with session_regenerate_id (closes #7380) + * bcbbb28: Throw exception if value is passed to VALUE_NONE input, long syntax + * 6b71513: fixed date type format pattern regex + * b5ded81: [Security] fixed usage of the salt for the bcrypt encoder (refs #8210) + * 842f3fa: do not re-register commands each time a Console\Application is run + * 0991cd0: [Process] moved env check to the Process class (refs #8227) + * 8764944: fix issue where $_ENV contains array vals + * 4139936: [DomCrawler] Fix handling file:// without a host + * e65723c: fix-progressbar-start + * aa79393: also consider alias in Container::has() + * de289d2: [Form] corrected interface bind() method defined against in deprecation notice + * 0c0a3e9: [Console] fixed regression when calling a command foo:bar if there is another one like foo:bar:baz (closes #8245) + * 849f3ed: [Finder] Fix SplFileInfo::getContents isn't working with ssh2 protocol + * 6d2135b: force the Content-Type to html in the web profiler controllers + +* 2.3.1 (2013-06-11) + + * 25e3abd: fix many-to-many Propel1 ModelChoiceList + * bce6bd2: [DomCrawler] Fixed a fatal error when setting a value in a malformed field name. + * e3561ce: [FrameworkBundle] Fixed OutOfBoundException when session handler_id is null + * 81b122d: [DependencyInjection] Add support for aliases of aliases + regression test + * 445b2e3: [Console] fix status code when Exception::getCode returns something like 0.1 + * bbfde62: Fixed exit code for exceptions with error code 0 + * d8c0ef7: [DependencyInjection] Rename ContainerBuilder::$aliases to avoid conflicting with the parent class + * bb797ee: [DependencyInjection] Remove get*Alias*Service methods from compiled containers + * 379f5e0: [DependencyInjection] Fix aliased access of shared services, fixes #8096 + * afad9c7: instantiate valid commands only + +* 2.3.0 (2013-06-03) + + * e93fc7a: [FrameworkBundle] set the dispatcher in the console application + * 2038329: [Form] [Validator] Fixed post_max_size = 0 bug (Issue #8065) + * 554ab9f: [Console] renamed ConsoleForExceptionEvent into ConsoleExceptionEvent + * fd151fd: [Security] Fixed the check if an interface exists. + * c8e5503: [FrameworkBundle] removed HttpFoundation classes from HttpKernel cache + * 169c0b9: [Finder] Fix iteration fails with non-rewindable streams + * 45b68e0: [Finder] Fix unexpected duplicate sub path related AppendIterator issue + * 13ba4ea: fix logger in regards to DebugLoggerInterface + * 97b38ed: Added type of return value in VoterInterface. + * 79a842a: [Console] Add namespace support back in to list command + * 5321600: Fixed two bugs in HttpCache + * 435012f: [Config] Adding the previous exception message into the FileLoaderLoadException so it's more easily seen + * 5c317b7: [Console] fix and refactor exit code handling + * 1469953: [CssSelector] Fix :nth-last-child() translation + * 2d9027d: [CssSelector] Fix :nth-last-child() translation + * 91b8490: Fix Crawler::children() to not trigger a notice for childless node + +* 2.3.0-RC1 (2013-05-16) + + * 95f356b: remove check for PHP bug #50731 + * 8f54da7: [BrowserKit] should not follow redirects if status code is not 30x + * f41ac06: changed all version deps to accepts all upcoming Symfony versions + * a4e3ebf: [DomCrawler] Fixed the Crawler::html() method for PHP versions earlier than 5.3.6. + * 3beaf52: [Security] Disabled the BCryptPasswordEncoder tests for PHP versions lower than 5.3.7. + +* 2.3.0-BETA2 (2013-05-10) + + * 97bee20: Pass exceptions from the ExceptionListener to Monolog + * be42dbc: [HttpFoundation][File][UploadedFile] Fix guessClientExtension() method + * a5441b2: Fixed parsing of leading blank lines in folded scalars. Closes #7989. + * e8d5d16: Fixed Loader import + * bd0c48c: [Console] moved the IO configuration to its own method + * fdb4b1f: [Console] moved --help support to allow proper behavior with other passed options + * dd0e138: Eased translationNodeVisitor overriding in TranslationExtension + * 853f681: fixed request scope issues (refs #7457) + * 60edc58: Fixed fatal error in normalize/denormalizeObject. + * 78e3710: ProxyManager Bridge + * 41805c0: [Crawler] Add proper validation of node argument of method add + * 7933971: [Form] Added radio button for empty value to expanded single-choice fields + * 0586c7e: made some optimization when parsing YAML files + * 1856df3: [Security] fixed wrong merge (refs #4776) + * 5b7e1e6: added a missing check for the provider key + * f1c2ab7: [DependencyInjection] Add a method map to avoid computing method names from service names + * ea633f5: [HttpKernel] Avoid updating the context if the request did not change + * 997d549: [HttpFoundation] Avoid a few unnecessary str_replace() calls + * f5e7f24: [HttpFoundation] Optimize ServerBag::getHeaders() + * 59b78c7: [Validator] Fixed: $traverse and $deep is passed to the visitor from Validator::validate() + * bcb5400: [Form] Fixed transform()/reverseTransform() to always throw TransformationFailedExceptions + * 7b2ebbf: [Form] Fixed: String validation groups are never interpreted as callbacks + * 0610750: if the repository method returns an array ensure that it's internal poin... + * dcced01: [Form] Improved multi-byte handling of NumberToLocalizedStringTransformer + * 90a20d7: [Translation] Made translation domain defaults in Translator consistent with TranslatorInterface + * 549a308: [Form] Fixed CSRF error messages to be translated and added "csrf_message" option diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md index 1f04ffcff6741..ecb2a18af809a 100644 --- a/UPGRADE-2.1.md +++ b/UPGRADE-2.1.md @@ -901,7 +901,7 @@ public function guessPattern($class, $property) { if (/* condition */) { - return new ValueGuess('.{' . $minLength . ',}', Guess::LOW_CONFIDENCE); + return new ValueGuess('.{'.$minLength.',}', Guess::LOW_CONFIDENCE); } } ``` @@ -1189,7 +1189,7 @@ public function isPropertyValid(ExecutionContext $context) { // ... - $propertyPath = $context->getPropertyPath() . '.property'; + $propertyPath = $context->getPropertyPath().'.property'; $context->setPropertyPath($propertyPath); $context->addViolation('Error Message', array(), null); } diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md index 524a8f7e1d999..7900a4775bb7f 100644 --- a/UPGRADE-2.2.md +++ b/UPGRADE-2.2.md @@ -21,6 +21,29 @@ Note: The function is the preferred way. +#### Deprecations + + * The `standalone` option is deprecated and will 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. + + + Before: + + ``` + {% render 'BlogBundle:Post:list' with { 'limit': 2 }, {'standalone': true} %} + {% render 'BlogBundle:Post:list' with { 'limit': 2 }, {'standalone': false} %} + {% render 'BlogBundle:Post:list' with { 'limit': 2 }, {'standalone': 'js'} %} + ``` + + After: + + ``` + {{ render(controller('BlogBundle:Post:list', { 'limit': 2 }), { 'strategy': 'esi'}) }} + {{ render(controller('BlogBundle:Post:list', { 'limit': 2 }), { 'strategy': 'inline'}) }} + {{ render(controller('BlogBundle:Post:list', { 'limit': 2 }), { 'strategy': 'hinclude'}) }} + ``` + + ### HttpFoundation * The MongoDbSessionHandler default field names and timestamp type have changed. @@ -441,7 +464,7 @@ $path .= '.'; } - $context->getGraphWalker()->walkReference($someObject, $group, $path . 'myProperty', false); + $context->getGraphWalker()->walkReference($someObject, $group, $path.'myProperty', false); } } ``` diff --git a/UPGRADE-2.3.md b/UPGRADE-2.3.md new file mode 100644 index 0000000000000..02b36e66f2415 --- /dev/null +++ b/UPGRADE-2.3.md @@ -0,0 +1,279 @@ +UPGRADE FROM 2.2 to 2.3 +======================= + +Form +---- + + * Although this was not officially supported nor documented, it was possible to + set the option "validation_groups" to false, resulting in the group "Default" + being validated. Now, if you set "validation_groups" to false, the validation + of a form will be skipped (except for a few integrity checks on the form). + + If you want to validate a form in group "Default", you should either + explicitly set "validation_groups" to "Default" or alternatively set it to + null. + + Before: + + ``` + // equivalent notations for validating in group "Default" + "validation_groups" => null + "validation_groups" => "Default" + "validation_groups" => false + + // notation for skipping validation + "validation_groups" => array() + ``` + + After: + + ``` + // equivalent notations for validating in group "Default" + "validation_groups" => null + "validation_groups" => "Default" + + // equivalent notations for skipping validation + "validation_groups" => false + "validation_groups" => array() + ``` + * The array type hint from DataMapperInterface was removed. You should adapt + implementations of that interface accordingly. + + Before: + + ``` + use Symfony\Component\Form\DataMapperInterface; + + class MyDataMapper + { + public function mapFormsToData(array $forms, $data) + { + // ... + } + + public function mapDataToForms($data, array $forms) + { + // ... + } + } + ``` + + After: + + ``` + use Symfony\Component\Form\DataMapperInterface; + + class MyDataMapper + { + public function mapFormsToData($forms, $data) + { + // ... + } + + public function mapDataToForms($data, $forms) + { + // ... + } + } + ``` + + Instead of an array, the methods here are now passed a + RecursiveIteratorIterator containing an InheritDataAwareIterator by default, + so you don't need to handle forms inheriting their parent data (former + "virtual forms") in the data mapper anymore. + + Before: + + ``` + use Symfony\Component\Form\Util\VirtualFormAwareIterator; + + public function mapFormsToData(array $forms, $data) + { + $iterator = new \RecursiveIteratorIterator( + new VirtualFormAwareIterator($forms) + ); + + foreach ($iterator as $form) { + // ... + } + } + ``` + + After: + + ``` + public function mapFormsToData($forms, $data) + { + foreach ($forms as $form) { + // ... + } + } + ``` + + * The *_SET_DATA events are now guaranteed to be fired *after* the children + were added by the FormBuilder (unless setData() is called manually). Before, + the *_SET_DATA events were sometimes thrown before adding child forms, + which made it impossible to remove child forms dynamically. + + A consequence of this change is that you need to set the "auto_initialize" + option to `false` for `FormInterface` instances that you pass to + `FormInterface::add()`: + + Before: + + ``` + $form = $factory->create('form'); + $form->add($factory->createNamed('field', 'text')); + ``` + + This code will now throw an exception with the following message: + + Automatic initialization is only supported on root forms. You should set the + "auto_initialize" option to false on the field "field". + + Consequently, you need to set the "auto_initialize" option: + + After (Alternative 1): + + ``` + $form = $factory->create('form'); + $form->add($factory->createNamed('field', 'text', array( + 'auto_initialize' => false, + ))); + ``` + + The problem also disappears if you work with `FormBuilder` instances instead + of `Form` instances: + + After (Alternative 2): + + ``` + $builder = $factory->createBuilder('form'); + $builder->add($factory->createBuilder('field', 'text')); + $form = $builder->getForm(); + ``` + + The best solution is in most cases to let `add()` handle the field creation: + + After (Alternative 3): + + ``` + $form = $factory->create('form'); + $form->add('field', 'text'); + ``` + + After (Alternative 4): + + ``` + $builder = $factory->createBuilder('form'); + $builder->add('field', 'text'); + $form = $builder->getForm(); + ``` + +PropertyAccess +-------------- + + * PropertyAccessor was changed to continue its search for a property or method + even if a non-public match was found. This means that the property "author" + in the following class will now correctly be found: + + ``` + class Article + { + public $author; + + private function getAuthor() + { + // ... + } + } + ``` + + Although this is uncommon, similar cases exist in practice. + + Instead of the PropertyAccessDeniedException that was thrown here, the more + generic NoSuchPropertyException is thrown now if no public property nor + method are found by the PropertyAccessor. PropertyAccessDeniedException was + removed completely. + + Before: + + ``` + use Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException; + use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; + + try { + $value = $accessor->getValue($article, 'author'); + } catch (PropertyAccessDeniedException $e) { + // Method/property was found but not public + } catch (NoSuchPropertyException $e) { + // Method/property was not found + } + ``` + + After: + + ``` + use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; + + try { + $value = $accessor->getValue($article, 'author'); + } catch (NoSuchPropertyException $e) { + // Method/property was not found or not public + } + ``` + +DomCrawler +---------- + + * `Crawler::each()` and `Crawler::reduce()` now return Crawler instances + instead of DomElement instances: + + Before: + + ``` + $data = $crawler->each(function ($node, $i) { + return $node->nodeValue; + }); + ``` + + After: + + ``` + $data = $crawler->each(function ($crawler, $i) { + return $crawler->text(); + }); + ``` + +Console +------- + + * New verbosity levels have been added, therefore if you used to do check + the output verbosity level directly for VERBOSITY_VERBOSE you probably + want to update it to a greater than comparison: + + Before: + + ``` + if (OutputInterface::VERBOSITY_VERBOSE === $output->getVerbosity()) { ... } + ``` + + After: + + ``` + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { ... } + ``` + +BrowserKit +---------- + + * If you are receiving responses with non-3xx Status Code and Location header + please be aware that you won't be able to use auto-redirects on these kind + of responses. + + If you are correctly passing 3xx Status Code with Location header, you + don't have to worry about the change. + + If you were using responses with Location header and non-3xx Status Code, + you have to update your code to manually create another request to URL + grabbed from the Location header. \ No newline at end of file diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index df10137624312..dd647eaf9ea12 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -3,8 +3,8 @@ UPGRADE FROM 2.x to 3.0 ### ClassLoader - * The `UniversalClassLoader` class has been removed in favor of `ClassLoader`. The only difference is that some method - names are different: + * The `UniversalClassLoader` class has been removed in favor of + `ClassLoader`. The only difference is that some method names are different: * `registerNamespaces()` -> `addPrefixes()` * `registerPrefixes()` -> `addPrefixes()` @@ -18,6 +18,199 @@ UPGRADE FROM 2.x to 3.0 `DebugClassLoader`. The difference is that the constructor now takes a loader to wrap. +### Form + + * The methods `Form::bind()` and `Form::isBound()` were removed. You should + use `Form::submit()` and `Form::isSubmitted()` instead. + + Before: + + ``` + $form->bind(array(...)); + ``` + + After: + + ``` + $form->submit(array(...)); + ``` + + * Passing a `Symfony\Component\HttpFoundation\Request` instance, as was + supported by `FormInterface::bind()`, is not possible with + `FormInterface::submit()` anymore. You should use `FormInterface::handleRequest()` + instead. + + Before: + + ``` + if ('POST' === $request->getMethod()) { + $form->bind($request); + + if ($form->isValid()) { + // ... + } + } + ``` + + After: + + ``` + $form->handleRequest($request); + + if ($form->isValid()) { + // ... + } + ``` + + If you want to test whether the form was submitted separately, you can use + the method `isSubmitted()`: + + ``` + $form->handleRequest($request); + + if ($form->isSubmitted()) { + // ... + + if ($form->isValid()) { + // ... + } + } + ``` + + * The events PRE_BIND, BIND and POST_BIND were renamed to PRE_SUBMIT, SUBMIT + and POST_SUBMIT. + + Before: + + ``` + $builder->addEventListener(FormEvents::PRE_BIND, function (FormEvent $event) { + // ... + }); + ``` + + After: + + ``` + $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { + // ... + }); + ``` + + * The option "virtual" was renamed to "inherit_data". + + Before: + + ``` + $builder->add('address', 'form', array( + 'virtual' => true, + )); + ``` + + After: + + ``` + $builder->add('address', 'form', array( + 'inherit_data' => true, + )); + ``` + + * The class VirtualFormAwareIterator was renamed to InheritDataAwareIterator. + + Before: + + ``` + use Symfony\Component\Form\Util\VirtualFormAwareIterator; + + $iterator = new VirtualFormAwareIterator($forms); + ``` + + After: + + ``` + use Symfony\Component\Form\Util\InheritDataAwareIterator; + + $iterator = new InheritDataAwareIterator($forms); + ``` + + * The `TypeTestCase` class was moved from the `Symfony\Component\Form\Tests\Extension\Core\Type` namespace to the `Symfony\Component\Form\Test` namespace. + + Before: + + ``` + use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase + + class MyTypeTest extends TypeTestCase + { + // ... + } + ``` + + After: + + ``` + use Symfony\Component\Form\Test\TypeTestCase; + + class MyTypeTest extends TypeTestCase + { + // ... + } + ``` + + * The `FormItegrationTestCase` and `FormPerformanceTestCase` classes were moved form the `Symfony\Component\Form\Tests` namespace to the `Symfony\Component\Form\Test` namespace. + + +### FrameworkBundle + + * The `enctype` method of the `form` helper was removed. You should use the + new method `start` instead. + + Before: + + ``` +
enctype($form) ?>> + ... +
+ ``` + + After: + + ``` + start($form) ?> + ... + end($form) ?> + ``` + + The method and action of the form default to "POST" and the current + document. If you want to change these values, you can set them explicitly in + the controller. + + Alternative 1: + + ``` + $form = $this->createForm('my_form', $formData, array( + 'method' => 'PUT', + 'action' => $this->generateUrl('target_route'), + )); + ``` + + Alternative 2: + + ``` + $form = $this->createFormBuilder($formData) + // ... + ->setMethod('PUT') + ->setAction($this->generateUrl('target_route')) + ->getForm(); + ``` + + It is also possible to override the method and the action in the template: + + ``` + start($form, array('method' => 'GET', 'action' => 'http://example.com')) ?> + ... + end($form) ?> + ``` + ### HttpKernel * The `Symfony\Component\HttpKernel\Log\LoggerInterface` has been removed in @@ -34,9 +227,52 @@ UPGRADE FROM 2.x to 3.0 * `Symfony\Bridge\Monolog\Logger` * `Symfony\Component\HttpKernel\Log\NullLogger` + * The `Symfony\Component\HttpKernel\Kernel::init()` method has been removed. + + * The following classes have been renamed as they have been moved to the + Debug component: + + * `Symfony\Component\HttpKernel\Debug\ErrorHandler` -> `Symfony\Component\Debug\ErrorHandler` + * `Symfony\Component\HttpKernel\Debug\ExceptionHandler` -> `Symfony\Component\Debug\ExceptionHandler` + * `Symfony\Component\HttpKernel\Exception\FatalErrorException` -> `Symfony\Component\Debug\Exception\FatalErrorException` + * `Symfony\Component\HttpKernel\Exception\FlattenException` -> `Symfony\Component\Debug\Exception\FlattenException` + * The `Symfony\Component\HttpKernel\EventListener\ExceptionListener` now passes the Request format as the `_format` argument instead of `format`. +### Locale + + * The Locale component was removed and replaced by the Intl component. + Instead of the methods in `Symfony\Component\Locale\Locale`, you should use + these equivalent methods in `Symfony\Component\Intl\Intl` now: + + * `Locale::getDisplayCountries()` -> `Intl::getRegionBundle()->getCountryNames()` + * `Locale::getCountries()` -> `array_keys(Intl::getRegionBundle()->getCountryNames())` + * `Locale::getDisplayLanguages()` -> `Intl::getLanguageBundle()->getLanguageNames()` + * `Locale::getLanguages()` -> `array_keys(Intl::getLanguageBundle()->getLanguageNames())` + * `Locale::getDisplayLocales()` -> `Intl::getLocaleBundle()->getLocaleNames()` + * `Locale::getLocales()` -> `array_keys(Intl::getLocaleBundle()->getLocaleNames())` + +### PropertyAccess + + * Renamed `PropertyAccess::getPropertyAccessor` to `createPropertyAccessor`. + + Before: + + ``` + use Symfony\Component\PropertyAccess\PropertyAccess; + + $accessor = PropertyAccess::getPropertyAccessor(); + ``` + + After: + + ``` + use Symfony\Component\PropertyAccess\PropertyAccess; + + $accessor = PropertyAccess::createPropertyAccessor(); + ``` + ### Routing * Some route settings have been renamed: @@ -82,10 +318,99 @@ UPGRADE FROM 2.x to 3.0 $route->setSchemes('https'); ``` +### Translator + + * The `Translator::setFallbackLocale()` method has been removed in favor of + `Translator::setFallbackLocales()`. + ### Twig Bridge * The `render` tag is deprecated in favor of the `render` function. + * The `form_enctype` helper was removed. You should use the new `form_start` + function instead. + + Before: + + ``` +
+ ... +
+ ``` + + After: + + ``` + {{ form_start(form) }} + ... + {{ form_end(form) }} + ``` + + The method and action of the form default to "POST" and the current + document. If you want to change these values, you can set them explicitly in + the controller. + + Alternative 1: + + ``` + $form = $this->createForm('my_form', $formData, array( + 'method' => 'PUT', + 'action' => $this->generateUrl('target_route'), + )); + ``` + + Alternative 2: + + ``` + $form = $this->createFormBuilder($formData) + // ... + ->setMethod('PUT') + ->setAction($this->generateUrl('target_route')) + ->getForm(); + ``` + + It is also possible to override the method and the action in the template: + + ``` + {{ form_start(form, {'method': 'GET', 'action': 'http://example.com'}) }} + ... + {{ form_end(form) }} + ``` + +### Validator + + * The constraints `Optional` and `Required` were moved to the + `Symfony\Component\Validator\Constraints\` namespace. You should adapt + the path wherever you used them. + + Before: + + ``` + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @Assert\Collection({ + * "foo" = @Assert\Collection\Required(), + * "bar" = @Assert\Collection\Optional(), + * }) + */ + private $property; + ``` + + After: + + ``` + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @Assert\Collection({ + * "foo" = @Assert\Required(), + * "bar" = @Assert\Optional(), + * }) + */ + private $property; + ``` + ### Yaml * The ability to pass file names to `Yaml::parse()` has been removed. diff --git a/autoload.php.dist b/autoload.php.dist index 2be61eb189fcf..0bb2bc3a85271 100644 --- a/autoload.php.dist +++ b/autoload.php.dist @@ -10,8 +10,6 @@ $loader = require_once __DIR__.'/vendor/autoload.php'; use Doctrine\Common\Annotations\AnnotationRegistry; -if (!function_exists('intl_get_error_code')) { - require_once __DIR__.'/src/Symfony/Component/Locale/Resources/stubs/functions.php'; -} - AnnotationRegistry::registerLoader(array($loader, 'loadClass')); + +return $loader; diff --git a/composer.json b/composer.json index 24320d448783b..9e2eee3214267 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=5.3.3", + "symfony/icu": "~1.0", "doctrine/common": "~2.2", "twig/twig": "~1.11", "psr/log": "~1.0" @@ -28,6 +29,7 @@ "symfony/console": "self.version", "symfony/css-selector": "self.version", "symfony/dependency-injection": "self.version", + "symfony/debug": "self.version", "symfony/doctrine-bridge": "self.version", "symfony/dom-crawler": "self.version", "symfony/event-dispatcher": "self.version", @@ -37,12 +39,14 @@ "symfony/framework-bundle": "self.version", "symfony/http-foundation": "self.version", "symfony/http-kernel": "self.version", + "symfony/intl": "self.version", "symfony/locale": "self.version", "symfony/monolog-bridge": "self.version", "symfony/options-resolver": "self.version", "symfony/process": "self.version", "symfony/propel1-bridge": "self.version", "symfony/property-access": "self.version", + "symfony/proxy-manager-bridge": "self.version", "symfony/routing": "self.version", "symfony/security": "self.version", "symfony/security-bundle": "self.version", @@ -62,19 +66,22 @@ "doctrine/dbal": "~2.2", "doctrine/orm": "~2.2,>=2.2.3", "monolog/monolog": "~1.3", - "propel/propel1": "1.6.*" + "propel/propel1": "1.6.*", + "ircmaxell/password-compat": "1.0.*", + "ocramius/proxy-manager": ">=0.3.1,<0.4-dev" }, "autoload": { "psr-0": { "Symfony\\": "src/" }, "classmap": [ "src/Symfony/Component/HttpFoundation/Resources/stubs", - "src/Symfony/Component/Locale/Resources/stubs" - ] + "src/Symfony/Component/Intl/Resources/stubs" + ], + "files": [ "src/Symfony/Component/Intl/Resources/stubs/functions.php" ] }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bridge/Doctrine/.gitignore b/src/Symfony/Bridge/Doctrine/.gitignore index 44de97a36a6df..c49a5d8df5c65 100644 --- a/src/Symfony/Bridge/Doctrine/.gitignore +++ b/src/Symfony/Bridge/Doctrine/.gitignore @@ -1,4 +1,3 @@ vendor/ composer.lock phpunit.xml - diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 83bb444f34a3a..331a465b2fdba 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -51,7 +51,10 @@ protected function loadMappingInformation(array $objectManager, ContainerBuilder // automatically register bundle mappings foreach (array_keys($container->getParameter('kernel.bundles')) as $bundle) { if (!isset($objectManager['mappings'][$bundle])) { - $objectManager['mappings'][$bundle] = null; + $objectManager['mappings'][$bundle] = array( + 'mapping' => true, + 'is_bundle' => true, + ); } } } @@ -304,7 +307,7 @@ protected function detectMetadataDriver($dir, ContainerBuilder $container) protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName) { $cacheDriver = $objectManager[$cacheName.'_driver']; - $cacheDriverService = $this->getObjectManagerElementName($objectManager['name'] . '_' . $cacheName); + $cacheDriverService = $this->getObjectManagerElementName($objectManager['name'].'_'.$cacheName); switch ($cacheDriver['type']) { case 'service': diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php index c857ef93c3604..30479c8c01d78 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php @@ -51,21 +51,21 @@ public function process(ContainerBuilder $container) */ private function updateValidatorMappingFiles(ContainerBuilder $container, $mapping, $extension) { - if ( ! $container->hasParameter('validator.mapping.loader.' . $mapping . '_files_loader.mapping_files')) { + if (!$container->hasParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files')) { return; } - $files = $container->getParameter('validator.mapping.loader.' . $mapping . '_files_loader.mapping_files'); - $validationPath = 'Resources/config/validation.' . $this->managerType . '.' . $extension; + $files = $container->getParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files'); + $validationPath = 'Resources/config/validation.'.$this->managerType.'.'.$extension; foreach ($container->getParameter('kernel.bundles') as $bundle) { $reflection = new \ReflectionClass($bundle); - if (is_file($file = dirname($reflection->getFilename()) . '/' . $validationPath)) { + if (is_file($file = dirname($reflection->getFilename()).'/'.$validationPath)) { $files[] = realpath($file); $container->addResource(new FileResource($file)); } } - $container->setParameter('validator.mapping.loader.' . $mapping . '_files_loader.mapping_files', $files); + $container->setParameter('validator.mapping.loader.'.$mapping.'_files_loader.mapping_files', $files); } } diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php new file mode 100644 index 0000000000000..64c187469e1b0 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * Base class for the doctrine bundles to provide a compiler pass class that + * helps to register doctrine mappings. + * + * The compiler pass is meant to register the mappings with the metadata + * chain driver corresponding to one of the object managers. + * + * For concrete implementations that are easy to use, see the + * RegisterXyMappingsPass classes in the DoctrineBundle resp. + * DoctrineMongodbBundle, DoctrineCouchdbBundle and DoctrinePhpcrBundle. + * + * @author David Buchmann + */ +abstract class RegisterMappingsPass implements CompilerPassInterface +{ + /** + * DI object for the driver to use, either a service definition for a + * private service or a reference for a public service. + * @var Definition|Reference + */ + protected $driver; + + /** + * List of namespaces handled by the driver + * @var string[] + */ + protected $namespaces; + + /** + * List of potential container parameters that hold the object manager name + * to register the mappings with the correct metadata driver, for example + * array('acme.manager', 'doctrine.default_entity_manager') + * @var string[] + */ + protected $managerParameters; + + /** + * Naming pattern of the metadata chain driver service ids, for example + * 'doctrine.orm.%s_metadata_driver' + * @var string + */ + protected $driverPattern; + + /** + * A name for a parameter in the container. If set, this compiler pass will + * only do anything if the parameter is present. (But regardless of the + * value of that parameter. + * @var string + */ + protected $enabledParameter; + + /** + * @param Definition|Reference $driver driver DI definition or reference + * @param string[] $namespaces list of namespaces handled by $driver + * @param string[] $managerParameters list of container parameters + * that could hold the manager name + * @param string $driverPattern pattern to get the metadata driver service names + * @param string $enabledParameter service container parameter that must be + * present to enable the mapping. Set to false + * to not do any check, optional. + */ + public function __construct($driver, array $namespaces, array $managerParameters, $driverPattern, $enabledParameter = false) + { + $this->driver = $driver; + $this->namespaces = $namespaces; + $this->managerParameters = $managerParameters; + $this->driverPattern = $driverPattern; + $this->enabledParameter = $enabledParameter; + } + + /** + * Register mappings with the metadata drivers. + * + * @param ContainerBuilder $container + */ + public function process(ContainerBuilder $container) + { + if (!$this->enabled($container)) { + return; + } + + $mappingDriverDef = $this->getDriver($container); + + $chainDriverDefService = $this->getChainDriverServiceName($container); + $chainDriverDef = $container->getDefinition($chainDriverDefService); + foreach ($this->namespaces as $namespace) { + $chainDriverDef->addMethodCall('addDriver', array($mappingDriverDef, $namespace)); + } + } + + /** + * Get the service name of the metadata chain driver that the mappings + * should be registered with. The default implementation loops over the + * managerParameters and applies the first non-empty parameter it finds to + * the driverPattern. + * + * @param ContainerBuilder $container + * + * @return string a service definition name + * + * @throws ParameterNotFoundException if non of the managerParameters has a + * non-empty value. + */ + protected function getChainDriverServiceName(ContainerBuilder $container) + { + foreach ($this->managerParameters as $param) { + if ($container->hasParameter($param)) { + $name = $container->getParameter($param); + if ($name) { + return sprintf($this->driverPattern, $name); + } + } + } + + throw new ParameterNotFoundException('None of the managerParameters resulted in a valid name'); + } + + /** + * Create the service definition for the metadata driver. + * + * @param ContainerBuilder $container passed on in case an extending class + * needs access to the container. + * + * @return Definition|Reference the metadata driver to add to all chain drivers + */ + protected function getDriver(ContainerBuilder $container) + { + return $this->driver; + } + + /** + * Determine whether this mapping should be activated or not. This allows + * to take this decision with the container builder available. + * + * This default implementation checks if the class has the enabledParameter + * configured and if so if that parameter is present in the container. + * + * @param ContainerBuilder $container + * + * @return boolean whether this compiler pass really should register the mappings + */ + protected function enabled(ContainerBuilder $container) + { + return !$this->enabledParameter || $container->hasParameter($this->enabledParameter); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php index fabd25f7c2001..74042a7b41a31 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; -use Symfony\Component\Form\Exception\Exception; +use Symfony\Component\Form\Exception\RuntimeException; use Symfony\Component\Form\Exception\StringCastException; use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList; use Doctrine\Common\Persistence\ObjectManager; @@ -406,12 +406,12 @@ private function load() * * @return array The identifier values * - * @throws Exception If the entity does not exist in Doctrine's identity map + * @throws RuntimeException If the entity does not exist in Doctrine's identity map */ private function getIdentifierValues($entity) { if (!$this->em->contains($entity)) { - throw new Exception( + throw new RuntimeException( 'Entities passed to the choice field must be managed. Maybe ' . 'persist them in the entity manager?' ); diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 1423e2e250cc9..cd8f883f4975a 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -138,14 +138,6 @@ public function guessMaxLength($class, $property) } } - /** - * {@inheritDoc} - */ - public function guessMinLength($class, $property) - { - trigger_error('guessMinLength() is deprecated since version 2.1 and will be removed in 2.3.', E_USER_DEPRECATED); - } - /** * {@inheritDoc} */ diff --git a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php index 7b7f58003d015..41f36e0ae87b8 100644 --- a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php +++ b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php @@ -30,7 +30,7 @@ public static function getSubscribedEvents() { // Higher priority than core MergeCollectionListener so that this one // is called before - return array(FormEvents::BIND => array('onBind', 10)); + return array(FormEvents::SUBMIT => array('onBind', 10)); } public function onBind(FormEvent $event) diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index df6f2077f80eb..81623966c1087 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -12,7 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\Common\Persistence\ManagerRegistry; -use Symfony\Component\Form\Exception\Exception; +use Symfony\Component\Form\Exception\RuntimeException; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; @@ -144,7 +144,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) $em = $registry->getManagerForClass($options['class']); if (null === $em) { - throw new Exception(sprintf( + throw new RuntimeException(sprintf( 'Class "%s" seems not to be a managed Doctrine entity. ' . 'Did you forget to map it?', $options['class'] diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php new file mode 100644 index 0000000000000..999f469ff4cc2 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Security\RememberMe; + +use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type as DoctrineType; +use PDO, DateTime; + +/** + * This class provides storage for the tokens that is set in "remember me" + * cookies. This way no password secrets will be stored in the cookies on + * the client machine, and thus the security is improved. + * + * This depends only on doctrine in order to get a database connection + * and to do the conversion of the datetime column. + * + * In order to use this class, you need the following table in your database: + * CREATE TABLE `rememberme_token` ( + * `series` char(88) UNIQUE PRIMARY KEY NOT NULL, + * `value` char(88) NOT NULL, + * `lastUsed` datetime NOT NULL, + * `class` varchar(100) NOT NULL, + * `username` varchar(200) NOT NULL + * ); + */ +class DoctrineTokenProvider implements TokenProviderInterface +{ + /** + * Doctrine DBAL database connection + * F.ex. service id: doctrine.dbal.default_connection + * + * @var \Doctrine\DBAL\Connection + */ + private $conn; + + /** + * new DoctrineTokenProvider for the RemembeMe authentication service + * + * @param \Doctrine\DBAL\Connection $conn + */ + public function __construct(Connection $conn) + { + $this->conn = $conn; + } + + /** + * {@inheritdoc} + */ + public function loadTokenBySeries($series) + { + $sql = 'SELECT class, username, value, lastUsed' + . ' FROM rememberme_token WHERE series=:series'; + $paramValues = array('series' => $series); + $paramTypes = array('series' => PDO::PARAM_STR); + $stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + return new PersistentToken($row['class'], + $row['username'], + $series, + $row['value'], + new DateTime($row['lastUsed']) + ); + } + + throw new TokenNotFoundException('No token found.'); + } + + /** + * {@inheritdoc} + */ + public function deleteTokenBySeries($series) + { + $sql = 'DELETE FROM rememberme_token WHERE series=:series'; + $paramValues = array('series' => $series); + $paramTypes = array('series' => PDO::PARAM_STR); + $this->conn->executeUpdate($sql, $paramValues, $paramTypes); + } + + /** + * {@inheritdoc} + */ + public function updateToken($series, $tokenValue, DateTime $lastUsed) + { + $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed' + . ' WHERE series=:series'; + $paramValues = array('value' => $tokenValue, + 'lastUsed' => $lastUsed, + 'series' => $series); + $paramTypes = array('value' => PDO::PARAM_STR, + 'lastUsed' => DoctrineType::DATETIME, + 'series' => PDO::PARAM_STR); + $updated = $this->conn->executeUpdate($sql, $paramValues, $paramTypes); + if ($updated < 1) { + throw new TokenNotFoundException('No token found.'); + } + } + + /** + * {@inheritdoc} + */ + public function createNewToken(PersistentTokenInterface $token) + { + $sql = 'INSERT INTO rememberme_token' + . ' (class, username, series, value, lastUsed)' + . ' VALUES (:class, :username, :series, :value, :lastUsed)'; + $paramValues = array('class' => $token->getClass(), + 'username' => $token->getUsername(), + 'series' => $token->getSeries(), + 'value' => $token->getTokenValue(), + 'lastUsed' => $token->getLastUsed()); + $paramTypes = array('class' => PDO::PARAM_STR, + 'username' => PDO::PARAM_STR, + 'series' => PDO::PARAM_STR, + 'value' => PDO::PARAM_STR, + 'lastUsed' => DoctrineType::DATETIME); + $this->conn->executeUpdate($sql, $paramValues, $paramTypes); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/EntityChoiceListTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/EntityChoiceListTest.php index a3ce7503436cd..cce4578798347 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/EntityChoiceListTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/EntityChoiceListTest.php @@ -70,7 +70,7 @@ protected function tearDown() } /** - * @expectedException \Symfony\Component\Form\Exception\FormException + * @expectedException \Symfony\Component\Form\Exception\StringCastException * @expectedMessage Entity "Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity" passed to the choice field must have a "__toString()" method defined (or you can also override the "property" option). */ public function testEntitiesMustHaveAToStringMethod() @@ -97,7 +97,7 @@ public function testEntitiesMustHaveAToStringMethod() } /** - * @expectedException \Symfony\Component\Form\Exception\FormException + * @expectedException \Symfony\Component\Form\Exception\RuntimeException */ public function testChoicesMustBeManaged() { diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index 1fc6ae378e8a7..27a3951499846 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\Type; -use Symfony\Component\Form\Tests\FormPerformanceTestCase; +use Symfony\Component\Form\Test\FormPerformanceTestCase; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity; use Doctrine\ORM\Tools\SchemaTool; use Symfony\Bridge\Doctrine\Tests\DoctrineOrmTestCase; @@ -120,7 +120,7 @@ public function testCollapsedEntityField() */ public function testCollapsedEntityFieldWithChoices() { - $choices = $this->em->createQuery('SELECT c FROM ' . self::ENTITY_CLASS . ' c')->getResult(); + $choices = $this->em->createQuery('SELECT c FROM '.self::ENTITY_CLASS.' c')->getResult(); $this->setMaxRunningTime(1); for ($i = 0; $i < 40; ++$i) { @@ -139,7 +139,7 @@ public function testCollapsedEntityFieldWithChoices() */ public function testCollapsedEntityFieldWithPreferredChoices() { - $choices = $this->em->createQuery('SELECT c FROM ' . self::ENTITY_CLASS . ' c')->getResult(); + $choices = $this->em->createQuery('SELECT c FROM '.self::ENTITY_CLASS.' c')->getResult(); $this->setMaxRunningTime(1); for ($i = 0; $i < 40; ++$i) { diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 4d23db4ef827b..427323ff8afa3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -196,7 +196,7 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() }, )); - $field->bind('2'); + $field->submit('2'); } public function testSetDataSingleNull() @@ -248,7 +248,7 @@ public function testSubmitSingleExpandedNull() 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, )); - $field->bind(null); + $field->submit(null); $this->assertNull($field->getData()); $this->assertSame(array(), $field->getViewData()); @@ -262,7 +262,7 @@ public function testSubmitSingleNonExpandedNull() 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, )); - $field->bind(null); + $field->submit(null); $this->assertNull($field->getData()); $this->assertSame('', $field->getViewData()); @@ -275,7 +275,7 @@ public function testSubmitMultipleNull() 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, )); - $field->bind(null); + $field->submit(null); $this->assertEquals(new ArrayCollection(), $field->getData()); $this->assertSame(array(), $field->getViewData()); @@ -296,7 +296,7 @@ public function testSubmitSingleNonExpandedSingleIdentifier() 'property' => 'name', )); - $field->bind('2'); + $field->submit('2'); $this->assertTrue($field->isSynchronized()); $this->assertSame($entity2, $field->getData()); @@ -319,7 +319,7 @@ public function testSubmitSingleNonExpandedCompositeIdentifier() )); // the collection key is used here - $field->bind('1'); + $field->submit('1'); $this->assertTrue($field->isSynchronized()); $this->assertSame($entity2, $field->getData()); @@ -342,7 +342,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifier() 'property' => 'name', )); - $field->bind(array('1', '3')); + $field->submit(array('1', '3')); $expected = new ArrayCollection(array($entity1, $entity3)); @@ -370,7 +370,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifierForExistingData() $existing = new ArrayCollection(array(0 => $entity2)); $field->setData($existing); - $field->bind(array('1', '3')); + $field->submit(array('1', '3')); // entry with index 0 ($entity2) was replaced $expected = new ArrayCollection(array(0 => $entity1, 1 => $entity3)); @@ -399,7 +399,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifier() )); // because of the composite key collection keys are used - $field->bind(array('0', '2')); + $field->submit(array('0', '2')); $expected = new ArrayCollection(array($entity1, $entity3)); @@ -427,7 +427,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifierExistingData() $existing = new ArrayCollection(array(0 => $entity2)); $field->setData($existing); - $field->bind(array('0', '2')); + $field->submit(array('0', '2')); // entry with index 0 ($entity2) was replaced $expected = new ArrayCollection(array(0 => $entity1, 1 => $entity3)); @@ -454,7 +454,7 @@ public function testSubmitSingleExpanded() 'property' => 'name', )); - $field->bind('2'); + $field->submit('2'); $this->assertTrue($field->isSynchronized()); $this->assertSame($entity2, $field->getData()); @@ -480,7 +480,7 @@ public function testSubmitMultipleExpanded() 'property' => 'name', )); - $field->bind(array('1', '3')); + $field->submit(array('1', '3')); $expected = new ArrayCollection(array($entity1, $entity3)); @@ -510,7 +510,7 @@ public function testOverrideChoices() 'property' => 'name', )); - $field->bind('2'); + $field->submit('2'); $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); $this->assertTrue($field->isSynchronized()); @@ -535,7 +535,7 @@ public function testGroupByChoices() 'group_by' => 'groupName', )); - $field->bind('2'); + $field->submit('2'); $this->assertSame('2', $field->getViewData()); $this->assertEquals(array( @@ -599,7 +599,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier() 'property' => 'name', )); - $field->bind('3'); + $field->submit('3'); $this->assertFalse($field->isSynchronized()); $this->assertNull($field->getData()); @@ -620,7 +620,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesCompositeIdentifier( 'property' => 'name', )); - $field->bind('2'); + $field->submit('2'); $this->assertFalse($field->isSynchronized()); $this->assertNull($field->getData()); @@ -644,7 +644,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifie 'property' => 'name', )); - $field->bind('3'); + $field->submit('3'); $this->assertFalse($field->isSynchronized()); $this->assertNull($field->getData()); @@ -668,7 +668,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingle 'property' => 'name', )); - $field->bind('3'); + $field->submit('3'); $this->assertFalse($field->isSynchronized()); $this->assertNull($field->getData()); @@ -692,7 +692,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureCompos 'property' => 'name', )); - $field->bind('2'); + $field->submit('2'); $this->assertFalse($field->isSynchronized()); $this->assertNull($field->getData()); @@ -712,7 +712,7 @@ public function testSubmitSingleStringIdentifier() 'property' => 'name', )); - $field->bind('foo'); + $field->submit('foo'); $this->assertTrue($field->isSynchronized()); $this->assertSame($entity1, $field->getData()); @@ -734,7 +734,7 @@ public function testSubmitCompositeStringIdentifier() )); // the collection key is used here - $field->bind('0'); + $field->submit('0'); $this->assertTrue($field->isSynchronized()); $this->assertSame($entity1, $field->getData()); diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index a2f91c6463386..0d339afc6b8b5 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -95,7 +95,7 @@ public function validate($entity, Constraint $constraint) if (count($relatedId) > 1) { throw new ConstraintDefinitionException( "Associated entities are not allowed to have more than one identifier field to be " . - "part of a unique constraint in: " . $class->getName() . "#" . $fieldName + "part of a unique constraint in: ".$class->getName()."#".$fieldName ); } $criteria[$fieldName] = array_pop($relatedId); diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index a7a9ace23861b..e657db70d1bba 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -20,22 +20,22 @@ "doctrine/common": "~2.2" }, "require-dev": { - "symfony/stopwatch": ">=2.2,<2.3-dev", + "symfony/stopwatch": "~2.2", "symfony/dependency-injection": "~2.0", - "symfony/form": ">=2.2,<2.3-dev", - "symfony/http-kernel": ">=2.2,<2.3-dev", - "symfony/security": ">=2.2,<2.3-dev", - "symfony/validator": ">=2.2,<2.3-dev", + "symfony/form": "~2.2", + "symfony/http-kernel": "~2.2", + "symfony/security": "~2.2", + "symfony/validator": "~2.2", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.2", "doctrine/orm": "~2.2,>=2.2.3" }, "suggest": { - "symfony/form": "2.2.*", - "symfony/validator": "2.2.*", - "doctrine/data-fixtures": "1.0.*", - "doctrine/dbal": "~2.2", - "doctrine/orm": "~2.2,>=2.2.3" + "symfony/form": "", + "symfony/validator": "", + "doctrine/data-fixtures": "", + "doctrine/dbal": "", + "doctrine/orm": "" }, "autoload": { "psr-0": { "Symfony\\Bridge\\Doctrine\\": "" } @@ -44,7 +44,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bridge/Monolog/.gitignore b/src/Symfony/Bridge/Monolog/.gitignore index 44de97a36a6df..c49a5d8df5c65 100644 --- a/src/Symfony/Bridge/Monolog/.gitignore +++ b/src/Symfony/Bridge/Monolog/.gitignore @@ -1,4 +1,3 @@ vendor/ composer.lock phpunit.xml - diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index da5fff744db0b..b675069ef762a 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -62,6 +62,8 @@ public function getLogs() if ($logger = $this->getDebugLogger()) { return $logger->getLogs(); } + + return array(); } /** @@ -72,12 +74,14 @@ public function countErrors() if ($logger = $this->getDebugLogger()) { return $logger->countErrors(); } + + return 0; } /** * Returns a DebugLoggerInterface instance if one is registered with this logger. * - * @return DebugLoggerInterface A DebugLoggerInterface instance or null if none is registered + * @return DebugLoggerInterface|null A DebugLoggerInterface instance or null if none is registered */ private function getDebugLogger() { diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index b0d104f6eaae0..643bd9229a080 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=5.3.3", - "symfony/http-kernel": ">=2.2,<2.3-dev", + "symfony/http-kernel": "~2.2", "monolog/monolog": "~1.3" }, "autoload": { @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bridge/Propel1/.gitignore b/src/Symfony/Bridge/Propel1/.gitignore index 44de97a36a6df..c49a5d8df5c65 100644 --- a/src/Symfony/Bridge/Propel1/.gitignore +++ b/src/Symfony/Bridge/Propel1/.gitignore @@ -1,4 +1,3 @@ vendor/ composer.lock phpunit.xml - diff --git a/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php b/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php index 65356f47e222c..b10e3c278940b 100644 --- a/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php +++ b/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php @@ -15,7 +15,6 @@ use \BaseObject; use \Persistent; -use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\StringCastException; use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -84,7 +83,7 @@ public function __construct($class, $labelPath = null, $choices = null, $queryOb { $this->class = $class; - $queryClass = $this->class . 'Query'; + $queryClass = $this->class.'Query'; $query = new $queryClass(); $this->identifier = $query->getTableMap()->getPrimaryKeys(); @@ -198,7 +197,7 @@ public function getChoicesForValues(array $values) { if (!$this->loaded) { if (1 === count($this->identifier)) { - $filterBy = 'filterBy' . current($this->identifier)->getPhpName(); + $filterBy = 'filterBy'.current($this->identifier)->getPhpName(); return (array) $this->query->create() ->$filterBy($values) @@ -402,8 +401,6 @@ private function load() * @param object $model The model for which to get the identifier * * @return array - * - * @throws FormException If the model does not exist */ private function getIdentifierValues($model) { diff --git a/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php b/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php index a75b046a66b45..9f36afafec185 100644 --- a/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php +++ b/src/Symfony/Bridge/Propel1/Form/PropelTypeGuesser.php @@ -129,14 +129,6 @@ public function guessMaxLength($class, $property) } } - /** - * {@inheritDoc} - */ - public function guessMinLength($class, $property) - { - trigger_error('guessMinLength() is deprecated since version 2.1 and will be removed in 2.3.', E_USER_DEPRECATED); - } - /** * {@inheritDoc} */ diff --git a/src/Symfony/Bridge/Propel1/Security/User/PropelUserProvider.php b/src/Symfony/Bridge/Propel1/Security/User/PropelUserProvider.php index 11a14fdb54e2d..b92c951c45e58 100644 --- a/src/Symfony/Bridge/Propel1/Security/User/PropelUserProvider.php +++ b/src/Symfony/Bridge/Propel1/Security/User/PropelUserProvider.php @@ -53,7 +53,7 @@ class PropelUserProvider implements UserProviderInterface public function __construct($class, $property = null) { $this->class = $class; - $this->queryClass = $class . 'Query'; + $this->queryClass = $class.'Query'; $this->property = $property; } @@ -66,7 +66,7 @@ public function loadUserByUsername($username) $query = $queryClass::create(); if (null !== $this->property) { - $filter = 'filterBy' . ucfirst($this->property); + $filter = 'filterBy'.ucfirst($this->property); $query->$filter($username); } else { $query->filterByUsername($username); diff --git a/src/Symfony/Bridge/Propel1/composer.json b/src/Symfony/Bridge/Propel1/composer.json index f3c0b28777a75..3f64580182fee 100644 --- a/src/Symfony/Bridge/Propel1/composer.json +++ b/src/Symfony/Bridge/Propel1/composer.json @@ -19,11 +19,11 @@ "php": ">=5.3.3", "symfony/http-foundation": "~2.0", "symfony/http-kernel": "~2.0", - "symfony/form": ">=2.2,<2.3-dev", + "symfony/form": "~2.2", "propel/propel1": "1.6.*" }, "require-dev": { - "symfony/stopwatch": ">=2.2,<2.3-dev" + "symfony/stopwatch": "~2.2" }, "autoload": { "psr-0": { "Symfony\\Bridge\\Propel1\\": "" } @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bridge/ProxyManager/.gitignore b/src/Symfony/Bridge/ProxyManager/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md new file mode 100644 index 0000000000000..1f8f60c48bfed --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +2.3.0 +----- + + * First introduction of `Symfony\Bridge\ProxyManager` diff --git a/src/Symfony/Bridge/ProxyManager/LICENSE b/src/Symfony/Bridge/ProxyManager/LICENSE new file mode 100644 index 0000000000000..88a57f8d8da49 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php new file mode 100644 index 0000000000000..4364019345621 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.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\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\Configuration; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; +use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; + +/** + * Runtime lazy loading proxy generator. + * + * @author Marco Pivetta + */ +class RuntimeInstantiator implements InstantiatorInterface +{ + /** + * @var \ProxyManager\Factory\LazyLoadingValueHolderFactory + */ + private $factory; + + /** + * Constructor + */ + public function __construct() + { + $config = new Configuration(); + + $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + + $this->factory = new LazyLoadingValueHolderFactory($config); + } + + /** + * {@inheritDoc} + */ + public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator) + { + return $this->factory->createProxy( + $definition->getClass(), + function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) { + $proxy->setProxyInitializer(null); + + $wrappedInstance = call_user_func($realInstantiator); + + return true; + } + ); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php new file mode 100644 index 0000000000000..05f3ae1bffd8c --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.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\ProxyManager\LazyProxy\PhpDumper; + +use ProxyManager\Generator\ClassGenerator; +use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; +use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; + +/** + * Generates dumped php code of proxies via reflection. + * + * @author Marco Pivetta + */ +class ProxyDumper implements DumperInterface +{ + /** + * @var \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator + */ + private $proxyGenerator; + + /** + * @var \ProxyManager\GeneratorStrategy\BaseGeneratorStrategy + */ + private $classGenerator; + + /** + * Constructor + */ + public function __construct() + { + $this->proxyGenerator = new LazyLoadingValueHolderGenerator(); + $this->classGenerator = new BaseGeneratorStrategy(); + } + + /** + * {@inheritDoc} + */ + public function isProxyCandidate(Definition $definition) + { + return $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class); + } + + /** + * {@inheritDoc} + */ + public function getProxyFactoryCode(Definition $definition, $id) + { + $instantiation = 'return'; + + if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { + $instantiation .= " \$this->services['$id'] ="; + } elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { + $instantiation .= " \$this->services['$id'] = \$this->scopedServices['$scope']['$id'] ="; + } + + $methodName = 'get'.Container::camelize($id).'Service'; + $proxyClass = $this->getProxyClassName($definition); + + return <<setProxyInitializer(null); + + \$wrappedInstance = \$container->$methodName(false); + + return true; + } + ); + } + + +EOF; + } + + /** + * {@inheritDoc} + */ + public function getProxyCode(Definition $definition) + { + $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); + + $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); + + return $this->classGenerator->generate($generatedClass); + } + + /** + * Produces the proxy class name for the given definition. + * + * @param Definition $definition + * + * @return string + */ + private function getProxyClassName(Definition $definition) + { + return str_replace('\\', '', $definition->getClass()).'_'.spl_object_hash($definition); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/README.md b/src/Symfony/Bridge/ProxyManager/README.md new file mode 100644 index 0000000000000..a266a2600188e --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/README.md @@ -0,0 +1,15 @@ +ProxyManager Bridge +=================== + +Provides integration for [ProxyManager][1] with various Symfony2 components. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Bridge/ProxyManager/ + $ composer.phar install --dev + $ phpunit + +[1]: https://github.com/Ocramius/ProxyManager diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php new file mode 100644 index 0000000000000..4cc8c585a8425 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests; + +require_once __DIR__ . '/Fixtures/includes/foo.php'; + +use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Integration tests for {@see \Symfony\Component\DependencyInjection\ContainerBuilder} combined + * with the ProxyManager bridge + * + * @author Marco Pivetta + */ +class ContainerBuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService + */ + public function testCreateProxyServiceWithRuntimeInstantiator() + { + $builder = new ContainerBuilder(); + + $builder->setProxyInstantiator(new RuntimeInstantiator()); + + $builder->register('foo1', 'ProxyManagerBridgeFooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php'); + $builder->getDefinition('foo1')->setLazy(true); + + /* @var $foo1 \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $foo1 = $builder->get('foo1'); + + $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved on multiple subsequent calls'); + $this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1); + $this->assertInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1); + $this->assertFalse($foo1->isProxyInitialized()); + + $foo1->initializeProxy(); + + $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved after initialization'); + $this->assertTrue($foo1->isProxyInitialized()); + $this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1->getWrappedValueHolderValue()); + $this->assertNotInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1->getWrappedValueHolderValue()); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php new file mode 100644 index 0000000000000..a12ff2bcca372 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests\Dumper; + +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; + +/** + * Integration tests for {@see \Symfony\Component\DependencyInjection\Dumper\PhpDumper} combined + * with the ProxyManager bridge + * + * @author Marco Pivetta + */ +class PhpDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDumpContainerWithProxyService() + { + $container = new ContainerBuilder(); + + $container->register('foo', 'stdClass'); + $container->getDefinition('foo')->setLazy(true); + $container->compile(); + + $dumper = new PhpDumper($container); + + $dumper->setProxyDumper(new ProxyDumper()); + + $dumpedString = $dumper->dump(); + + $this->assertStringMatchesFormatFile( + __DIR__.'/../Fixtures/php/lazy_service_structure.txt', + $dumpedString, + '->dump() does generate proxy lazy loading logic.' + ); + } + + /** + * Verifies that the generated container retrieves the same proxy instance on multiple subsequent requests + */ + public function testDumpContainerWithProxyServiceWillShareProxies() + { + require_once __DIR__.'/../Fixtures/php/lazy_service.php'; + + $container = new \LazyServiceProjectServiceContainer(); + + /* @var $proxy \stdClass_c1d194250ee2e2b7d2eab8b8212368a8 */ + $proxy = $container->get('foo'); + $this->assertInstanceOf('stdClass_c1d194250ee2e2b7d2eab8b8212368a8', $proxy); + $this->assertSame($proxy, $container->get('foo')); + + $this->assertFalse($proxy->isProxyInitialized()); + + $proxy->initializeProxy(); + + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertSame($proxy, $container->get('foo')); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php new file mode 100644 index 0000000000000..1013a8c572325 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php @@ -0,0 +1,36 @@ +arguments = $arguments; + } + + public static function getInstance($arguments = array()) + { + $obj = new self($arguments); + $obj->called = true; + + return $obj; + } + + public function initialize() + { + $this->initialized = true; + } + + public function configure() + { + $this->configured = true; + } + + public function setBar($value = null) + { + $this->bar = $value; + } +} 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 new file mode 100644 index 0000000000000..e2158b8f9b678 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php @@ -0,0 +1,197 @@ +services = + $this->scopedServices = + $this->scopeStacks = array(); + + $this->set('service_container', $this); + + $this->scopes = array(); + $this->scopeChildren = array(); + } + + /** + * Gets the 'foo' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @param boolean $lazyLoad whether to try lazy-loading the service with a proxy + * + * @return stdClass A stdClass instance. + */ + public function getFooService($lazyLoad = true) + { + if ($lazyLoad) { + $container = $this; + + return $this->services['foo'] = new stdClass_c1d194250ee2e2b7d2eab8b8212368a8( + function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { + $proxy->setProxyInitializer(null); + + $wrappedInstance = $container->getFooService(false); + + return true; + } + ); + } + + return new \stdClass(); + } +} + +class stdClass_c1d194250ee2e2b7d2eab8b8212368a8 extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +{ + + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $valueHolder5157dd96e88c0 = null; + + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $initializer5157dd96e8924 = null; + + /** + * @override constructor for lazy initialization + * + * @param \Closure|null $initializer + */ + public function __construct($initializer) + { + $this->initializer5157dd96e8924 = $initializer; + } + + /** + * @param string $name + */ + public function __get($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__get', array('name' => $name)); + + return $this->valueHolder5157dd96e88c0->$name; + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__set', array('name' => $name, 'value' => $value)); + + $this->valueHolder5157dd96e88c0->$name = $value; + } + + /** + * @param string $name + */ + public function __isset($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__isset', array('name' => $name)); + + return isset($this->valueHolder5157dd96e88c0->$name); + } + + /** + * @param string $name + */ + public function __unset($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__unset', array('name' => $name)); + + unset($this->valueHolder5157dd96e88c0->$name); + } + + /** + * + */ + public function __clone() + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__clone', array()); + + $this->valueHolder5157dd96e88c0 = clone $this->valueHolder5157dd96e88c0; + } + + /** + * + */ + public function __sleep() + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__sleep', array()); + + return array('valueHolder5157dd96e88c0'); + } + + /** + * + */ + public function __wakeup() + { + } + + /** + * {@inheritDoc} + */ + public function setProxyInitializer(\Closure $initializer = null) + { + $this->initializer5157dd96e8924 = $initializer; + } + + /** + * {@inheritDoc} + */ + public function getProxyInitializer() + { + return $this->initializer5157dd96e8924; + } + + /** + * {@inheritDoc} + */ + public function initializeProxy() + { + return $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, 'initializeProxy', array()); + } + + /** + * {@inheritDoc} + */ + public function isProxyInitialized() + { + return null !== $this->valueHolder5157dd96e88c0; + } + + /** + * {@inheritDoc} + */ + public function getWrappedValueHolderValue() + { + return $this->valueHolder5157dd96e88c0; + } + +} 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 new file mode 100644 index 0000000000000..e1831fd9fa19f --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -0,0 +1,27 @@ +services['foo'] = new stdClass_%s( + function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { + $proxy->setProxyInitializer(null); + + $wrappedInstance = $container->getFooService(false); + + return true; + } + ); + } + + return new \stdClass(); + } +} + +class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +{%a}%A \ No newline at end of file diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php new file mode 100644 index 0000000000000..17d0deaf249ca --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests\Instantiator; + +use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator} + * + * @author Marco Pivetta + * + * @covers \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator + */ +class RuntimeInstantiatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var RuntimeInstantiator + */ + protected $instantiator; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->instantiator = new RuntimeInstantiator(); + } + + public function testInstantiateProxy() + { + $instance = new \stdClass(); + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $definition = new Definition('stdClass'); + $instantiator = function () use ($instance) { + return $instance; + }; + + /* @var $proxy \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $proxy = $this->instantiator->instantiateProxy($container, $definition, 'foo', $instantiator); + + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $proxy); + $this->assertInstanceOf('ProxyManager\Proxy\ValueHolderInterface', $proxy); + $this->assertFalse($proxy->isProxyInitialized()); + + $proxy->initializeProxy(); + + $this->assertSame($instance, $proxy->getWrappedValueHolderValue()); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php new file mode 100644 index 0000000000000..5fefbfac8c710 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests\Instantiator; + +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper} + * + * @author Marco Pivetta + * + * @covers \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper + */ +class ProxyDumperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ProxyDumper + */ + protected $dumper; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->dumper = new ProxyDumper(); + } + + /** + * @dataProvider getProxyCandidates + * + * @param Definition $definition + * @param bool $expected + */ + public function testIsProxyCandidate(Definition $definition, $expected) + { + $this->assertSame($expected, $this->dumper->isProxyCandidate($definition)); + } + + public function testGetProxyCode() + { + $definition = new Definition(__CLASS__); + + $definition->setLazy(true); + + $code = $this->dumper->getProxyCode($definition); + + $this->assertStringMatchesFormat( + '%Aclass SymfonyBridgeProxyManagerLazyProxyTestsInstantiatorProxyDumperTest%aextends%w' + . '\Symfony\Bridge\ProxyManager\LazyProxy\Tests\Instantiator%a', + $code + ); + } + + public function testGetProxyFactoryCode() + { + $definition = new Definition(__CLASS__); + + $definition->setLazy(true); + + $code = $this->dumper->getProxyFactoryCode($definition, 'foo'); + + $this->assertStringMatchesFormat( + '%wif ($lazyLoad) {%w$container = $this;%wreturn $this->services[\'foo\'] = new ' + . 'SymfonyBridgeProxyManagerLazyProxyTestsInstantiatorProxyDumperTest_%s(%wfunction ' + . '(&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {' + . '%w$proxy->setProxyInitializer(null);%w$wrappedInstance = $container->getFooService(false);' + . '%wreturn true;%w}%w);%w}%w', + $code + ); + } + + /** + * @return array + */ + public function getProxyCandidates() + { + $definitions = array( + array(new Definition(__CLASS__), true), + array(new Definition('stdClass'), true), + array(new Definition('foo' . uniqid()), false), + array(new Definition(), false), + ); + + array_map( + function ($definition) { + $definition[0]->setLazy(true); + }, + $definitions + ); + + return $definitions; + } +} diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json new file mode 100644 index 0000000000000..4d3abb604815e --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/proxy-manager-bridge", + "type": "symfony-bridge", + "description": "Symfony ProxyManager Bridge", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/dependency-injection": "~2.3", + "ocramius/proxy-manager": ">=0.3.1,<0.4-dev" + }, + "autoload": { + "psr-0": { + "Symfony\\Bridge\\ProxyManager\\": "" + } + }, + "target-dir": "Symfony/Bridge/ProxyManager", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist b/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist new file mode 100644 index 0000000000000..5e7c4337f91b5 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Bridge/Swiftmailer/.gitignore b/src/Symfony/Bridge/Swiftmailer/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Bridge/Swiftmailer/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Bridge/Swiftmailer/composer.json b/src/Symfony/Bridge/Swiftmailer/composer.json index e2d08202b6a1e..9e6c90b7bdaf6 100644 --- a/src/Symfony/Bridge/Swiftmailer/composer.json +++ b/src/Symfony/Bridge/Swiftmailer/composer.json @@ -20,7 +20,7 @@ "swiftmailer/swiftmailer": ">=4.2.0,<5.1-dev" }, "suggest": { - "symfony/http-kernel": "2.2.*" + "symfony/http-kernel": "" }, "autoload": { "psr-0": { "Symfony\\Bridge\\Swiftmailer\\": "" } @@ -29,7 +29,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bridge/Twig/.gitignore b/src/Symfony/Bridge/Twig/.gitignore index 44de97a36a6df..c49a5d8df5c65 100644 --- a/src/Symfony/Bridge/Twig/.gitignore +++ b/src/Symfony/Bridge/Twig/.gitignore @@ -1,4 +1,3 @@ vendor/ composer.lock phpunit.xml - diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 343c7743a3ca2..ad22216e40b87 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +2.3.0 +----- + + * added helpers form(), form_start() and form_end() + * deprecated form_enctype() in favor of form_start() + 2.2.0 ----- diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 34162af096aa2..429e65c519dfe 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -61,13 +61,16 @@ public function getTokenParsers() public function getFunctions() { return array( - new \Twig_SimpleFunction('form_enctype', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_widget', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_errors', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_label', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_row', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_rest', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('csrf_token', array($this->renderer, 'renderCsrfToken')), + 'form_enctype' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\FormEnctypeNode', array('is_safe' => array('html'))), + 'form_widget' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), + 'form_errors' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), + 'form_label' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), + 'form_row' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), + 'form_rest' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), + 'form' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\RenderBlockNode', array('is_safe' => array('html'))), + 'form_start' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\RenderBlockNode', array('is_safe' => array('html'))), + 'form_end' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\RenderBlockNode', array('is_safe' => array('html'))), + 'csrf_token' => new \Twig_Function_Method($this, 'renderer->renderCsrfToken'), ); } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index dc56c9d9771a3..775fe42ee5f6e 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -54,8 +54,6 @@ public function getFunctions() */ public function renderFragment($uri, $options = array()) { - $options = $this->handler->fixOptions($options); - $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline'; unset($options['strategy']); diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 1e280a48863f0..237c36c190c22 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -35,8 +35,8 @@ public function __construct(UrlGeneratorInterface $generator) public function getFunctions() { return array( - new \Twig_SimpleFunction('url', array($this, 'getUrl')), - new \Twig_SimpleFunction('path', array($this, 'getPath')), + new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), ); } @@ -50,6 +50,44 @@ public function getUrl($name, $parameters = array(), $schemeRelative = false) return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); } + /** + * Determines at compile time whether the generated URL will be safe and thus + * saving the unneeded automatic escaping for performance reasons. + * + * The URL generation process percent encodes non-alphanumeric characters. So there is no risk + * that malicious/invalid characters are part of the URL. The only character within an URL that + * must be escaped in html is the ampersand ("&") which separates query params. So we cannot mark + * the URL generation as always safe, but only when we are sure there won't be multiple query + * params. This is the case when there are none or only one constant parameter given. + * E.g. we know beforehand this will be safe: + * - path('route') + * - path('route', {'param': 'value'}) + * But the following may not: + * - path('route', var) + * - path('route', {'param': ['val1', 'val2'] }) // a sub-array + * - path('route', {'param1': 'value1', 'param2': 'value2'}) + * If param1 and param2 reference placeholder in the route, it would still be safe. But we don't know. + * + * @param \Twig_Node $argsNode The arguments of the path/url function + * + * @return array An array with the contexts the URL is safe + */ + public function isUrlGenerationSafe(\Twig_Node $argsNode) + { + // support named arguments + $paramsNode = $argsNode->hasNode('parameters') ? $argsNode->getNode('parameters') : ( + $argsNode->hasNode(1) ? $argsNode->getNode(1) : null + ); + + if (null === $paramsNode || $paramsNode instanceof \Twig_Node_Expression_Array && count($paramsNode) <= 2 && + (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof \Twig_Node_Expression_Constant) + ) { + return array('html'); + } + + return array(); + } + /** * Returns the name of the extension. * diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index 91fb796aefe55..81316e8fd4e6d 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -28,10 +28,14 @@ class TranslationExtension extends \Twig_Extension private $translator; private $translationNodeVisitor; - public function __construct(TranslatorInterface $translator) + public function __construct(TranslatorInterface $translator, \Twig_NodeVisitorInterface $translationNodeVisitor = null) { + if (!$translationNodeVisitor) { + $translationNodeVisitor = new TranslationNodeVisitor(); + } + $this->translator = $translator; - $this->translationNodeVisitor = new TranslationNodeVisitor(); + $this->translationNodeVisitor = $translationNodeVisitor; } public function getTranslator() diff --git a/src/Symfony/Bridge/Twig/Node/FormEnctypeNode.php b/src/Symfony/Bridge/Twig/Node/FormEnctypeNode.php new file mode 100644 index 0000000000000..0114b354d8b3c --- /dev/null +++ b/src/Symfony/Bridge/Twig/Node/FormEnctypeNode.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +/** + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use + * the helper "form_start()" instead. + */ +class FormEnctypeNode extends SearchAndRenderBlockNode +{ + public function compile(\Twig_Compiler $compiler) + { + parent::compile($compiler); + + // Uncomment this as soon as the deprecation note should be shown + // $compiler->write('trigger_error(\'The helper form_enctype(form) is deprecated since version 2.3 and will be removed in 3.0. Use form_start(form) instead.\', E_USER_DEPRECATED)'); + } +} diff --git a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php new file mode 100644 index 0000000000000..822a27279fa79 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +/** + * Compiles a call to {@link FormRendererInterface::renderBlock()}. + * + * The function name is used as block name. For example, if the function name + * is "foo", the block "foo" will be rendered. + * + * @author Bernhard Schussek + */ +class RenderBlockNode extends \Twig_Node_Expression_Function +{ + public function compile(\Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + $arguments = iterator_to_array($this->getNode('arguments')); + $compiler->write('$this->env->getExtension(\'form\')->renderer->renderBlock('); + + if (isset($arguments[0])) { + $compiler->subcompile($arguments[0]); + $compiler->raw(', \'' . $this->getAttribute('name') . '\''); + + if (isset($arguments[1])) { + $compiler->raw(', '); + $compiler->subcompile($arguments[1]); + } + } + + $compiler->raw(')'); + } +} diff --git a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php index dca1686098ec3..630e2638ddd6d 100644 --- a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php +++ b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php @@ -29,7 +29,7 @@ public function compile(\Twig_Compiler $compiler) if (isset($arguments[0])) { $compiler->subcompile($arguments[0]); - $compiler->raw(', \'' . $blockNameSuffix . '\''); + $compiler->raw(', \''.$blockNameSuffix.'\''); if (isset($arguments[1])) { if ('label' === $blockNameSuffix) { 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 9688751417dbc..239780f3c6b41 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 @@ -218,6 +218,29 @@ {% endspaceless %} {% endblock email_widget %} +{% block button_widget %} +{% spaceless %} + {% if label is empty %} + {% set label = name|humanize %} + {% endif %} + +{% endspaceless %} +{% endblock button_widget %} + +{% block submit_widget %} +{% spaceless %} + {% set type = type|default('submit') %} + {{ block('button_widget') }} +{% endspaceless %} +{% endblock submit_widget %} + +{% block reset_widget %} +{% spaceless %} + {% set type = type|default('reset') %} + {{ block('button_widget') }} +{% endspaceless %} +{% endblock reset_widget %} + {# Labels #} {% block form_label %} @@ -237,6 +260,8 @@ {% endspaceless %} {% endblock form_label %} +{% block button_label %}{% endblock %} + {# Rows #} {% block repeated_row %} @@ -259,12 +284,52 @@ {% endspaceless %} {% endblock form_row %} +{% block button_row %} +{% spaceless %} +
+ {{ form_widget(form) }} +
+{% endspaceless %} +{% endblock button_row %} + {% block hidden_row %} {{ form_widget(form) }} {% endblock hidden_row %} {# Misc #} +{% block form %} +{% spaceless %} + {{ form_start(form) }} + {{ form_widget(form) }} + {{ form_end(form) }} +{% endspaceless %} +{% endblock form %} + +{% block form_start %} +{% spaceless %} + {% set method = method|upper %} + {% if method in ["GET", "POST"] %} + {% set form_method = method %} + {% else %} + {% set form_method = "POST" %} + {% endif %} +
+ {% if form_method != method %} + + {% endif %} +{% endspaceless %} +{% endblock form_start %} + +{% block form_end %} +{% spaceless %} + {% if not render_rest is defined or render_rest %} + {{ form_rest(form) }} + {% endif %} +
+{% endspaceless %} +{% endblock form_end %} + {% block form_enctype %} {% spaceless %} {% if multipart %}enctype="multipart/form-data"{% endif %} @@ -317,14 +382,9 @@ {% endspaceless %} {% endblock widget_container_attributes %} -{# Deprecated in Symfony 2.1, to be removed in 2.3 #} - -{% block generic_label %}{{ block('form_label') }}{% endblock %} -{% block widget_choice_options %}{{ block('choice_widget_options') }}{% endblock %} -{% block field_widget %}{{ block('form_widget_simple') }}{% endblock %} -{% block field_label %}{{ block('form_label') }}{% endblock %} -{% block field_row %}{{ block('form_row') }}{% endblock %} -{% block field_enctype %}{{ block('form_enctype') }}{% endblock %} -{% block field_errors %}{{ block('form_errors') }}{% endblock %} -{% block field_rest %}{{ block('form_rest') }}{% endblock %} -{% block field_rows %}{{ block('form_rows') }}{% endblock %} +{% block button_attributes %} +{% spaceless %} + id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %} + {% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} +{% endspaceless %} +{% 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 63bd7d2787137..aed4f8d77042d 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 @@ -14,6 +14,17 @@ {% endspaceless %} {% endblock form_row %} +{% block button_row %} +{% spaceless %} + + + + {{ form_widget(form) }} + + +{% endspaceless %} +{% endblock button_row %} + {% block hidden_row %} {% spaceless %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index 903db69edcc27..c5c134bc1cad6 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -134,11 +134,16 @@ public function isSelectedChoiceProvider() */ public function testIsChoiceSelected($expected, $choice, $value) { - $choice = new ChoiceView($choice, $choice, $choice . ' label'); + $choice = new ChoiceView($choice, $choice, $choice.' label'); $this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value)); } + protected function renderForm(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form', $vars); + } + protected function renderEnctype(FormView $view) { return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype'); @@ -173,6 +178,16 @@ protected function renderRest(FormView $view, array $vars = array()) return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars); } + protected function renderStart(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_start', $vars); + } + + protected function renderEnd(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_end', $vars); + } + protected function setTheme(FormView $view, array $themes) { $this->extension->renderer->setTheme($view, $themes); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index da0d846e637b8..99a782178a9a4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -75,6 +75,11 @@ protected function tearDown() $this->extension = null; } + protected function renderForm(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form', $vars); + } + protected function renderEnctype(FormView $view) { return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype'); @@ -109,6 +114,16 @@ protected function renderRest(FormView $view, array $vars = array()) return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars); } + protected function renderStart(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_start', $vars); + } + + protected function renderEnd(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_end', $vars); + } + protected function setTheme(FormView $view, array $themes) { $this->extension->renderer->setTheme($view, $themes); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index c0dc42d2f35f0..077927cd6d5fc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -20,6 +20,8 @@ class HttpKernelExtensionTest extends TestCase { protected function setUp() { + parent::setUp(); + if (!class_exists('Symfony\Component\HttpKernel\HttpKernel')) { $this->markTestSkipped('The "HttpKernel" component is not available'); } @@ -49,16 +51,8 @@ protected function getFragmentHandler($return) $strategy->expects($this->once())->method('getName')->will($this->returnValue('inline')); $strategy->expects($this->once())->method('render')->will($return); - // simulate a master request - $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')->disableOriginalConstructor()->getMock(); - $event - ->expects($this->once()) - ->method('getRequest') - ->will($this->returnValue(Request::create('/'))) - ; - $renderer = new FragmentHandler(array($strategy)); - $renderer->onKernelRequest($event); + $renderer->setRequest(Request::create('/')); return $renderer; } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php new file mode 100644 index 0000000000000..3c5d762ca008e --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.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\Tests\Extension; + +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Tests\TestCase; + +class RoutingExtensionTest extends TestCase +{ + protected function setUp() + { + parent::setUp(); + + if (!class_exists('Symfony\Component\Routing\Route')) { + $this->markTestSkipped('The "Routing" component is not available'); + } + } + + /** + * @dataProvider getEscapingTemplates + */ + public function testEscaping($template, $mustBeEscaped) + { + $twig = new \Twig_Environment(null, array('debug' => true, 'cache' => false, 'autoescape' => true, 'optimizations' => 0)); + $twig->addExtension(new RoutingExtension($this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'))); + + $nodes = $twig->parse($twig->tokenize($template)); + + $this->assertSame($mustBeEscaped, $nodes->getNode('body')->getNode(0)->getNode('expr') instanceof \Twig_Node_Expression_Filter); + } + + public function getEscapingTemplates() + { + return array( + array('{{ path("foo") }}', false), + array('{{ path("foo", {}) }}', false), + array('{{ path("foo", { foo: "foo" }) }}', false), + array('{{ path("foo", foo) }}', true), + array('{{ path("foo", { foo: foo }) }}', true), + array('{{ path("foo", { foo: ["foo", "bar"] }) }}', true), + array('{{ path("foo", { foo: "foo", bar: "bar" }) }}', true), + + array('{{ path(name = "foo", parameters = {}) }}', false), + array('{{ path(name = "foo", parameters = { foo: "foo" }) }}', false), + array('{{ path(name = "foo", parameters = foo) }}', true), + array('{{ path(name = "foo", parameters = { foo: ["foo", "bar"] }) }}', true), + array('{{ path(name = "foo", parameters = { foo: foo }) }}', true), + array('{{ path(name = "foo", parameters = { foo: "foo", bar: "bar" }) }}', true), + ); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index b5d866679f4ef..2b9c553366f55 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -21,6 +21,8 @@ class TranslationExtensionTest extends TestCase { protected function setUp() { + parent::setUp(); + if (!class_exists('Symfony\Component\Translation\Translator')) { $this->markTestSkipped('The "Translation" component is not available'); } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index a6e2a687dc8b9..918d328a601e1 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -20,22 +20,22 @@ "twig/twig": "~1.11" }, "require-dev": { - "symfony/form": "2.2.*", - "symfony/http-kernel": ">=2.2,<2.3-dev", - "symfony/routing": ">=2.2,<2.3-dev", + "symfony/form": "~2.2", + "symfony/http-kernel": "~2.2", + "symfony/routing": "~2.2", "symfony/templating": "~2.1", - "symfony/translation": ">=2.0,<2.3-dev", + "symfony/translation": "~2.2", "symfony/yaml": "~2.0", - "symfony/security": ">=2.0,<2.3-dev" + "symfony/security": "~2.0" }, "suggest": { - "symfony/form": "2.2.*", - "symfony/http-kernel": "2.2.*", - "symfony/routing": "2.2.*", - "symfony/templating": "2.2.*", - "symfony/translation": "2.2.*", - "symfony/yaml": "2.2.*", - "symfony/security": "2.2.*" + "symfony/form": "", + "symfony/http-kernel": "", + "symfony/routing": "", + "symfony/templating": "", + "symfony/translation": "", + "symfony/yaml": "", + "symfony/security": "" }, "autoload": { "psr-0": { "Symfony\\Bridge\\Twig\\": "" } @@ -44,7 +44,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/.gitignore b/src/Symfony/Bundle/FrameworkBundle/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 7d6321e819452..0e9393ca29308 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,25 @@ CHANGELOG ========= +2.3.0 +----- + + * [BC BREAK] added a way to disable the profiler (when disabling the profiler, it is now completely removed) + To get the same "disabled" behavior as before, set `enabled` to `true` and `collect` to `false` + * [BC BREAK] the `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass` was moved + to `Component\HttpKernel\DependencyInjection\RegisterListenersPass` + * added ControllerNameParser::build() which converts a controller short notation (a:b:c) to a class::method notation + * added possibility to run PHP built-in server in production environment + * added possibility to load the serializer component in the service container + * added route debug information when using the `router:match` command + * added `TimedPhpEngine` + * added `--clean` option to the `translation:update` command + * added `http_method_override` option + * added support for default templates per render tag + * added FormHelper::form(), FormHelper::start() and FormHelper::end() + * deprecated FormHelper::enctype() in favor of FormHelper::start() + * RedirectController actions now receive the Request instance via the method signature. + 2.2.0 ----- @@ -17,10 +36,10 @@ CHANGELOG * replaced Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver by Symfony\Component\HttpKernel\Controller\TraceableControllerResolver * replaced Symfony\Component\HttpKernel\Debug\ContainerAwareTraceableEventDispatcher by Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher * added Client::enableProfiler() - * A new parameter has been added to the DIC: `router.request_context.base_url` + * a new parameter has been added to the DIC: `router.request_context.base_url` You can customize it for your functional tests or for generating urls with the right base url when your are in the cli context. - * Added support for default templates per render tag + * added support for default templates per render tag 2.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Client.php b/src/Symfony/Bundle/FrameworkBundle/Client.php index 52efc80619ada..181964976903b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Client.php +++ b/src/Symfony/Bundle/FrameworkBundle/Client.php @@ -85,7 +85,7 @@ public function enableProfiler() } /** - * Makes a request. + * {@inheritdoc} * * @param Request $request A Request instance * @@ -113,6 +113,10 @@ protected function doRequest($request) /** * {@inheritdoc} + * + * @param Request $request A Request instance + * + * @return Response A Response instance */ protected function doRequestInProcess($request) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 831c682e4be7d..863e14b22b520 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -80,13 +80,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $filesystem = $this->getContainer()->get('filesystem'); // Create the bundles directory otherwise symlink will fail. - $filesystem->mkdir($targetArg.'/bundles/', 0777); + $bundlesDir = $targetArg.'/bundles/'; + $filesystem->mkdir($bundlesDir, 0777); $output->writeln(sprintf("Installing assets using the %s option", $input->getOption('symlink') ? 'symlink' : 'hard copy')); foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { if (is_dir($originDir = $bundle->getPath().'/Resources/public')) { - $bundlesDir = $targetArg.'/bundles/'; $targetDir = $bundlesDir.preg_replace('/bundle$/', '', strtolower($bundle->getName())); $output->writeln(sprintf('Installing assets for %s into %s', $bundle->getNamespace(), $targetDir)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index dcefd6857b82b..d384659259988 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -144,14 +144,6 @@ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = tr } } - /** - * @deprecated to be removed in 2.3 - */ - protected function getTempSuffix() - { - return ''; - } - /** * @param KernelInterface $parent * @param string $namespace diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 5343a18a14adc..f9e2d403c3dfa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -32,7 +32,7 @@ protected function configure() $this ->setName('config:dump-reference') ->setDefinition(array( - new InputArgument('name', InputArgument::REQUIRED, 'The Bundle or extension alias') + new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle or extension alias') )) ->setDescription('Dumps default configuration for an extension') ->setHelp(<<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() : '')); + } + + return; + } + $extension = null; if (preg_match('/Bundle$/', $name)) { @@ -74,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$extension) { - throw new \LogicException('No extensions with configuration available for "'.$name.'"'); + throw new \LogicException(sprintf('No extensions with configuration available for "%s"', $name)); } $message = 'Default configuration for "'.$name.'"'; @@ -90,7 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$extension) { - throw new \LogicException('No extension with alias "'.$name.'" is enabled'); + throw new \LogicException(sprintf('No extension with alias "%s" is enabled', $name)); } $message = 'Default configuration for extension with alias: "'.$name.'"'; @@ -99,14 +109,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $configuration = $extension->getConfiguration(array(), $containerBuilder); if (!$configuration) { - throw new \LogicException('The extension with alias "'.$extension->getAlias(). - '" does not have it\'s getConfiguration() method setup'); + throw new \LogicException(sprintf('The extension with alias "%s" does not have it\'s getConfiguration() method setup', $extension->getAlias())); } if (!$configuration instanceof ConfigurationInterface) { - throw new \LogicException( - 'Configuration class "'.get_class($configuration). - '" should implement ConfigurationInterface in order to be dumpable'); + throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable', get_class($configuration))); } $output->writeln($message); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 289f06f5a276d..c2f17fa19defc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -44,7 +44,9 @@ protected function configure() 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('tags', null, InputOption::VALUE_NONE, 'Displays tagged services for an application') + 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') )) ->setDescription('Displays current services for an application') ->setHelp(<<php %command.full_name% --tag=form.type + +Use the --parameters option to display all parameters: + + php %command.full_name% --parameters + +Display a specific parameter by specifying his name with the --parameter option: + + php %command.full_name% --parameter=kernel.debug EOF ) ; @@ -80,26 +90,36 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $name = $input->getArgument('name'); + $this->validateInput($input); $this->containerBuilder = $this->getContainerBuilder(); - $tag = $input->getOption('tag'); - if ($input->getOption('tags')) { - if ($tag || $input->getArgument('name')) { - throw new \InvalidArgumentException('The --tags option cannot be combined with the --tag option or the service name argument.'); - } + 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) { - if ($input->getArgument('name')) { - throw new \InvalidArgumentException('The --tag option cannot be combined with the service name argument.'); - } - $serviceIds = array_keys($this->containerBuilder->findTaggedServiceIds($tag)); } else { $serviceIds = $this->containerBuilder->getServiceIds(); @@ -108,6 +128,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // sort so that it reads like an index of services asort($serviceIds); + $name = $input->getArgument('name'); if ($name) { $this->outputService($output, $name); } else { @@ -115,6 +136,25 @@ protected function execute(InputInterface $input, OutputInterface $output) } } + protected function validateInput(InputInterface $input) + { + $options = array('tags', 'tag', 'parameters', 'parameter'); + + $optionsCount = 0; + foreach ($options as $option) { + if ($input->getOption($option)) { + $optionsCount++; + } + } + + $name = $input->getArgument('name'); + if ((null !== $name) && ($optionsCount > 0)) { + throw new \InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined with the service name argument.'); + } elseif ((null === $name) && $optionsCount > 1) { + throw new \InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined together.'); + } + } + protected function outputServices(OutputInterface $output, $serviceIds, $showPrivate = false, $showTagAttributes = null) { // set the label to specify public or public+private @@ -278,6 +318,48 @@ protected function outputService(OutputInterface $output, $serviceId) } } + 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. * @@ -366,4 +448,13 @@ protected function outputTags(OutputInterface $output, $showPrivate = false) $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/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 9859743ff7e2a..d581af54f92c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -184,7 +184,7 @@ private function formatConfigs(array $array) $string = ''; ksort($array); foreach ($array as $name => $value) { - $string .= ($string ? "\n" . str_repeat(' ', 13) : '') . $name . ': ' . $this->formatValue($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 1ccfa082407d3..405996902b6a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; @@ -76,6 +77,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(sprintf('Route "%s" almost matches but %s', $trace['name'], lcfirst($trace['log']))); } elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) { $output->writeln(sprintf('Route "%s" matches', $trace['name'])); + + $routerDebugcommand = $this->getApplication()->find('router:debug'); + $output->writeln(''); + $routerDebugcommand->run(new ArrayInput(array('name' => $trace['name'])), $output); + $matches = true; } elseif ($input->getOption('verbose')) { $output->writeln(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log'])); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php index f62a15de28665..1d01e04c1296e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php @@ -78,10 +78,16 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $env = $this->getContainer()->getParameter('kernel.environment'); + + if ('prod' === $env) { + $output->writeln('Running PHP built-in server in production environment is NOT recommended!'); + } + $router = $input->getOption('router') ?: $this ->getContainer() ->get('kernel') - ->locateResource('@FrameworkBundle/Resources/config/router.php') + ->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env)) ; $output->writeln(sprintf("Server running on %s\n", $input->getArgument('address'))); @@ -90,7 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $builder->setWorkingDirectory($input->getOption('docroot')); $builder->setTimeout(null); $builder->getProcess()->run(function ($type, $buffer) use ($output) { - if (OutputInterface::VERBOSITY_VERBOSE === $output->getVerbosity()) { + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $output->write($buffer); } }); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 6d9db047f263d..3bf1d857f34d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -12,12 +12,13 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Translation\Catalogue\DiffOperation; +use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Translation\MessageCatalogue; -use Symfony\Component\Yaml\Yaml; /** * A command that parse templates to extract translation messages and add them into the translation files. @@ -51,6 +52,10 @@ protected function configure() new InputOption( 'force', null, InputOption::VALUE_NONE, 'Should the update be done' + ), + new InputOption( + 'clean', null, InputOption::VALUE_NONE, + 'Should clean not found messages' ) )) ->setDescription('Updates the translation file') @@ -94,26 +99,41 @@ protected function execute(InputInterface $input, OutputInterface $output) $bundleTransPath = $foundBundle->getPath().'/Resources/translations'; $output->writeln(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $foundBundle->getName())); - // create catalogue - $catalogue = new MessageCatalogue($input->getArgument('locale')); - // load any messages from templates + $extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); $output->writeln('Parsing templates'); $extractor = $this->getContainer()->get('translation.extractor'); $extractor->setPrefix($input->getOption('prefix')); - $extractor->extract($foundBundle->getPath().'/Resources/views/', $catalogue); + $extractor->extract($foundBundle->getPath().'/Resources/views/', $extractedCatalogue); // load any existing messages from the translation files + $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $output->writeln('Loading translation files'); $loader = $this->getContainer()->get('translation.loader'); - $loader->loadMessages($bundleTransPath, $catalogue); + $loader->loadMessages($bundleTransPath, $currentCatalogue); + + // process catalogues + $operation = $input->getOption('clean') + ? new DiffOperation($currentCatalogue, $extractedCatalogue) + : new MergeOperation($currentCatalogue, $extractedCatalogue); // show compiled list of messages if ($input->getOption('dump-messages') === true) { - foreach ($catalogue->getDomains() as $domain) { + foreach ($operation->getDomains() as $domain) { $output->writeln(sprintf("\nDisplaying messages for domain %s:\n", $domain)); - $output->writeln(Yaml::dump($catalogue->all($domain), 10)); + $newKeys = array_keys($operation->getNewMessages($domain)); + $allKeys = array_keys($operation->getMessages($domain)); + foreach (array_diff($allKeys, $newKeys) as $id) { + $output->writeln($id); + } + foreach ($newKeys as $id) { + $output->writeln(sprintf('%s', $id)); + } + foreach (array_keys($operation->getObsoleteMessages($domain)) as $id) { + $output->writeln(sprintf('%s', $id)); + } } + if ($input->getOption('output-format') == 'xliff') { $output->writeln('Xliff output version is 1.2'); } @@ -122,7 +142,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // save the files if ($input->getOption('force') === true) { $output->writeln('Writing files'); - $writer->writeTranslations($catalogue, $input->getOption('output-format'), array('path' => $bundleTransPath)); + $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath)); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 695c819015c04..9d7e4ff1a9996 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Console; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -66,12 +67,24 @@ public function getKernel() */ public function doRun(InputInterface $input, OutputInterface $output) { + $this->kernel->boot(); + if (!$this->commandsRegistered) { $this->registerCommands(); $this->commandsRegistered = true; } + $container = $this->kernel->getContainer(); + + foreach ($this->all() as $command) { + if ($command instanceof ContainerAwareInterface) { + $command->setContainer($container); + } + } + + $this->setDispatcher($container->get('event_dispatcher')); + if (true === $input->hasParameterOption(array('--shell', '-s'))) { $shell = new Shell($this); $shell->setProcessIsolation($input->hasParameterOption(array('--process-isolation'))); @@ -85,8 +98,6 @@ public function doRun(InputInterface $input, OutputInterface $output) protected function registerCommands() { - $this->kernel->boot(); - foreach ($this->kernel->getBundles() as $bundle) { if ($bundle instanceof Bundle) { $bundle->registerCommands($this); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php index 667ff587de4b7..4b1c665a94bc7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php @@ -39,7 +39,7 @@ public function __construct(KernelInterface $kernel) * * @param string $controller A short notation controller (a:b:c) * - * @return string A string with class::method + * @return string A string in the class::method notation * * @throws \InvalidArgumentException when the specified bundle is not enabled * or the controller cannot be found @@ -47,7 +47,7 @@ public function __construct(KernelInterface $kernel) public function parse($controller) { if (3 != count($parts = explode(':', $controller))) { - throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid a:b:c controller string.', $controller)); + throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "a:b:c" controller string.', $controller)); } list($bundle, $controller, $action) = $parts; @@ -71,4 +71,33 @@ public function parse($controller) throw new \InvalidArgumentException($msg); } + + /** + * Converts a class::method notation to a short one (a:b:c). + * + * @param string $controller A string in the class::method notation + * + * @return string A short notation controller (a:b:c) + * + * @throws \InvalidArgumentException when the controller is not valid or cannot be found in any bundle + */ + public function build($controller) + { + if (0 === preg_match('#^(.*?\\\\Controller\\\\(.+)Controller)::(.+)Action$#', $controller, $match)) { + throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "class::method" string.', $controller)); + } + + $className = $match[1]; + $controllerName = $match[2]; + $actionName = $match[3]; + foreach ($this->kernel->getBundles() as $name => $bundle) { + if (0 !== strpos($className, $bundle->getNamespace())) { + continue; + } + + return sprintf('%s:%s:%s', $name, $controllerName, $actionName); + } + + throw new \InvalidArgumentException(sprintf('Unable to find a bundle that defines controller "%s".', $controller)); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index 7a255edc90a2d..8d515d4bcb9de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -13,6 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -32,19 +33,27 @@ class RedirectController extends ContainerAware * In case the route name is empty, the status code will be 404 when permanent is false * and 410 otherwise. * - * @param string $route The route name to redirect to - * @param Boolean $permanent Whether the redirection is permanent + * @param Request $request The request instance + * @param string $route The route name to redirect to + * @param Boolean $permanent Whether the redirection is permanent + * @param Boolean|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore * * @return Response A Response instance */ - public function redirectAction($route, $permanent = false) + public function redirectAction(Request $request, $route, $permanent = false, $ignoreAttributes = false) { if ('' == $route) { return new Response(null, $permanent ? 410 : 404); } - $attributes = $this->container->get('request')->attributes->get('_route_params'); - unset($attributes['route'], $attributes['permanent']); + $attributes = array(); + if (false === $ignoreAttributes || is_array($ignoreAttributes)) { + $attributes = $request->attributes->get('_route_params'); + unset($attributes['route'], $attributes['permanent'], $attributes['ignoreAttributes']); + if ($ignoreAttributes) { + $attributes = array_diff_key($attributes, array_flip($ignoreAttributes)); + } + } return new RedirectResponse($this->container->get('router')->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $permanent ? 301 : 302); } @@ -58,6 +67,7 @@ public function redirectAction($route, $permanent = false) * In case the path is empty, the status code will be 404 when permanent is false * and 410 otherwise. * + * @param Request $request The request instance * @param string $path The absolute path or URL to redirect to * @param Boolean $permanent Whether the redirect is permanent or not * @param string|null $scheme The URL scheme (null to keep the current one) @@ -66,7 +76,7 @@ public function redirectAction($route, $permanent = false) * * @return Response A Response instance */ - public function urlRedirectAction($path, $permanent = false, $scheme = null, $httpPort = null, $httpsPort = null) + public function urlRedirectAction(Request $request, $path, $permanent = false, $scheme = null, $httpPort = null, $httpsPort = null) { if ('' == $path) { return new Response(null, $permanent ? 410 : 404); @@ -79,7 +89,6 @@ public function urlRedirectAction($path, $permanent = false, $scheme = null, $ht return new RedirectResponse($path, $statusCode); } - $request = $this->container->get('request'); if (null === $scheme) { $scheme = $request->getScheme(); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php index ad93d7b419bba..20591c89cac32 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php @@ -12,16 +12,20 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\Config\ConfigCache; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\Filesystem\Filesystem; class CompilerDebugDumpPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - $cache = new ConfigCache($this->getCompilerLogFilename($container), false); - $cache->write(implode("\n", $container->getCompiler()->getLog())); + $filesystem = new Filesystem(); + $filesystem->dumpFile( + $this->getCompilerLogFilename($container), + implode("\n", $container->getCompiler()->getLog()), + 0666 & ~umask() + ); } public static function getCompilerLogFilename(ContainerInterface $container) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php index 1457d7cf2354d..a9916cdbdc1c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php @@ -14,7 +14,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\XmlDumper; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Filesystem\Filesystem; /** * Dumps the ContainerBuilder to a cache file so that it can be used by @@ -28,7 +28,11 @@ class ContainerBuilderDebugDumpPass implements CompilerPassInterface public function process(ContainerBuilder $container) { $dumper = new XmlDumper($container); - $cache = new ConfigCache($container->getParameter('debug.container.dump'), false); - $cache->write($dumper->dump()); + $filesystem = new Filesystem(); + $filesystem->dumpFile( + $container->getParameter('debug.container.dump'), + $dumper->dump(), + 0666 & ~umask() + ); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RegisterKernelListenersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RegisterKernelListenersPass.php deleted file mode 100644 index ad406eb6a3d90..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/RegisterKernelListenersPass.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * 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\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; - -class RegisterKernelListenersPass implements CompilerPassInterface -{ - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('event_dispatcher')) { - return; - } - - $definition = $container->getDefinition('event_dispatcher'); - - foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $events) { - foreach ($events as $event) { - $priority = isset($event['priority']) ? $event['priority'] : 0; - - if (!isset($event['event'])) { - throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); - } - - if (!isset($event['method'])) { - $event['method'] = 'on'.preg_replace_callback(array( - '/(?<=\b)[a-z]/i', - '/[^a-z0-9]/i', - ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); - $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); - } - - $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); - } - } - - foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) { - // We must assume that the class value has been correctly filled, even if the service is created by a factory - $class = $container->getDefinition($id)->getClass(); - - $refClass = new \ReflectionClass($class); - $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; - if (!$refClass->implementsInterface($interface)) { - throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); - } - - $definition->addMethodCall('addSubscriberService', array($id, $class)); - } - } -} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SerializerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SerializerPass.php new file mode 100644 index 0000000000000..aa449dd4c83ee --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/SerializerPass.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\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as + * encoders and normalizers to the Serializer service. + * + * @author Javier Lopez + */ +class SerializerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('serializer')) { + return; + } + + // Looks for all the services tagged "serializer.normalizer" and adds them to the Serializer service + $normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container); + $container->getDefinition('serializer')->replaceArgument(0, $normalizers); + + // Looks for all the services tagged "serializer.encoders" and adds them to the Serializer service + $encoders = $this->findAndSortTaggedServices('serializer.encoder', $container); + $container->getDefinition('serializer')->replaceArgument(1, $encoders); + } + + private function findAndSortTaggedServices($tagName, ContainerBuilder $container) + { + $services = $container->findTaggedServiceIds($tagName); + + if (empty($services)) { + throw new \RuntimeException(sprintf('You must tag at least one service as "%s" to use the Serializer service', $tagName)); + } + + $sortedServices = array(); + foreach ($services as $serviceId => $tags) { + foreach ($tags as $tag) { + $priority = isset($tag['priority']) ? $tag['priority'] : 0; + $sortedServices[$priority][] = new Reference($serviceId); + } + } + + krsort($sortedServices); + + // Flatten the array + return call_user_func_array('array_merge', $sortedServices); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 185a48bd16f33..79a61e821d103 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -26,8 +26,6 @@ class Configuration implements ConfigurationInterface * Generates the configuration tree builder. * * @return TreeBuilder The tree builder - * - * @throws \RuntimeException When using the deprecated 'charset' setting */ public function getConfigTreeBuilder() { @@ -36,23 +34,11 @@ public function getConfigTreeBuilder() $rootNode ->children() - ->scalarNode('charset') - ->defaultNull() - ->beforeNormalization() - ->ifTrue(function($v) { return null !== $v; }) - ->then(function($v) { - $message = 'The charset setting is deprecated. Just remove it from your configuration file.'; - - if ('UTF-8' !== $v) { - $message .= sprintf('You need to define a getCharset() method in your Application Kernel class that returns "%s".', $v); - } - - throw new \RuntimeException($message); - }) - ->end() - ->end() ->scalarNode('secret')->end() - ->scalarNode('trust_proxy_headers')->defaultFalse()->end() // @deprecated, to be removed in 2.3 + ->scalarNode('http_method_override') + ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests.") + ->defaultTrue() + ->end() ->arrayNode('trusted_proxies') ->beforeNormalization() ->ifTrue(function($v) { return !is_array($v) && !is_null($v); }) @@ -60,7 +46,21 @@ public function getConfigTreeBuilder() ->end() ->prototype('scalar') ->validate() - ->ifTrue(function($v) { return !empty($v) && !filter_var($v, FILTER_VALIDATE_IP); }) + ->ifTrue(function($v) { + if (empty($v)) { + return false; + } + + if (false !== strpos($v, '/')) { + list($v, $mask) = explode('/', $v, 2); + + if (strcmp($mask, (int) $mask) || $mask < 1 || $mask > (false !== strpos($v, ':') ? 128 : 32)) { + return true; + } + } + + return !filter_var($v, FILTER_VALIDATE_IP); + }) ->thenInvalid('Invalid proxy IP "%s"') ->end() ->end() @@ -88,6 +88,7 @@ public function getConfigTreeBuilder() $this->addTranslatorSection($rootNode); $this->addValidationSection($rootNode); $this->addAnnotationsSection($rootNode); + $this->addSerializerSection($rootNode); return $treeBuilder; } @@ -145,6 +146,7 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode) ->info('profiler configuration') ->canBeEnabled() ->children() + ->booleanNode('collect')->defaultTrue()->end() ->booleanNode('only_exceptions')->defaultFalse()->end() ->booleanNode('only_master_requests')->defaultFalse()->end() ->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end() @@ -204,16 +206,6 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->info('session configuration') ->canBeUnset() ->children() - ->booleanNode('auto_start') - ->info('DEPRECATED! Session starts on demand') - ->defaultFalse() - ->beforeNormalization() - ->ifTrue(function($v) { return null !== $v; }) - ->then(function($v) { - throw new \RuntimeException('The auto_start setting is deprecated. Just remove it from your configuration file.'); - }) - ->end() - ->end() ->scalarNode('storage_id')->defaultValue('session.storage.native')->end() ->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end() ->scalarNode('name')->end() @@ -226,11 +218,6 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->scalarNode('gc_probability')->end() ->scalarNode('gc_maxlifetime')->end() ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() - ->scalarNode('lifetime')->info('DEPRECATED! Please use: cookie_lifetime')->end() - ->scalarNode('path')->info('DEPRECATED! Please use: cookie_path')->end() - ->scalarNode('domain')->info('DEPRECATED! Please use: cookie_domain')->end() - ->booleanNode('secure')->info('DEPRECATED! Please use: cookie_secure')->end() - ->booleanNode('httponly')->info('DEPRECATED! Please use: cookie_httponly')->end() ->end() ->end() ->end() @@ -418,4 +405,16 @@ private function addAnnotationsSection(ArrayNodeDefinition $rootNode) ->end() ; } + + private function addSerializerSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('serializer') + ->info('serializer configuration') + ->canBeEnabled() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index b4decdfefc21d..6d274e07860a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -48,6 +48,8 @@ public function load(array $configs, ContainerBuilder $container) // will be used and everything will still work as expected. $loader->load('translation.xml'); + $loader->load('debug_prod.xml'); + if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); @@ -66,12 +68,9 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('kernel.secret', $config['secret']); } - $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); + $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); - - // @deprecated, to be removed in 2.3 - $container->setParameter('kernel.trust_proxy_headers', $config['trust_proxy_headers']); - + $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); $container->setParameter('kernel.default_locale', $config['default_locale']); if (!empty($config['test'])) { @@ -103,15 +102,11 @@ public function load(array $configs, ContainerBuilder $container) $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader); - $this->addClassesToCompile(array( - 'Symfony\\Component\\HttpFoundation\\ParameterBag', - 'Symfony\\Component\\HttpFoundation\\HeaderBag', - 'Symfony\\Component\\HttpFoundation\\FileBag', - 'Symfony\\Component\\HttpFoundation\\ServerBag', - 'Symfony\\Component\\HttpFoundation\\Request', - 'Symfony\\Component\\HttpFoundation\\Response', - 'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag', + if (isset($config['serializer']) && $config['serializer']['enabled']) { + $loader->load('serializer.xml'); + } + $this->addClassesToCompile(array( 'Symfony\\Component\\Config\\FileLocator', 'Symfony\\Component\\EventDispatcher\\Event', @@ -208,6 +203,13 @@ private function registerFragmentsConfiguration(array $config, ContainerBuilder */ private function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { + if (!$this->isConfigEnabled($container, $config)) { + // this is needed for the WebProfiler to work even if the profiler is disabled + $container->setParameter('data_collector.templates', array()); + + return; + } + $loader->load('profiling.xml'); $loader->load('collectors.xml'); @@ -253,7 +255,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ } } - if (!$this->isConfigEnabled($container, $config)) { + if (!$config['collect']) { $container->getDefinition('profiler')->addMethodCall('disable', array()); } } @@ -311,19 +313,13 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c } } - //we deprecated session options without cookie_ prefix, but we are still supporting them, - //Let's merge the ones that were supplied without prefix - foreach (array('lifetime', 'path', 'domain', 'secure', 'httponly') as $key) { - if (!isset($options['cookie_'.$key]) && isset($config[$key])) { - $options['cookie_'.$key] = $config[$key]; - } - } $container->setParameter('session.storage.options', $options); // session handler (the internal callback registered with PHP session management) if (null == $config['handler_id']) { // Set the handler class to be null $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']); } @@ -333,6 +329,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $this->addClassesToCompile(array( 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy', @@ -370,6 +367,9 @@ private function registerTemplatingConfiguration(array $config, $ide, ContainerB if ($container->getParameter('kernel.debug')) { $loader->load('templating_debug.xml'); + + $container->setDefinition('templating.engine.php', $container->findDefinition('debug.templating.engine.php')); + $container->setAlias('debug.templating.engine.php', 'templating.engine.php'); } // create package definitions and add them to the assets helper @@ -532,7 +532,10 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder // Use the "real" translator instead of the identity default $container->setAlias('translator', 'translator.default'); $translator = $container->findDefinition('translator.default'); - $translator->addMethodCall('setFallbackLocale', array($config['fallback'])); + if (!is_array($config['fallback'])) { + $config['fallback'] = array($config['fallback']); + } + $translator->addMethodCall('setFallbackLocales', array($config['fallback'])); // Discover translation directories $dirs = array(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Fragment/ContainerAwareHIncludeFragmentRenderer.php b/src/Symfony/Bundle/FrameworkBundle/Fragment/ContainerAwareHIncludeFragmentRenderer.php index 82c6a0b2b7f3b..698d979082055 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Fragment/ContainerAwareHIncludeFragmentRenderer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Fragment/ContainerAwareHIncludeFragmentRenderer.php @@ -32,7 +32,7 @@ public function __construct(ContainerInterface $container, UriSigner $signer = n { $this->container = $container; - parent::__construct(null, $signer, $globalDefaultTemplate); + parent::__construct(null, $signer, $globalDefaultTemplate, $container->getParameter('kernel.charset')); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 2919146d1023f..d5de61cd10dd4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -15,7 +15,6 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass; @@ -26,11 +25,13 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FragmentRendererPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Scope; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass; /** * Bundle. @@ -43,8 +44,10 @@ public function boot() { if ($trustedProxies = $this->container->getParameter('kernel.trusted_proxies')) { Request::setTrustedProxies($trustedProxies); - } elseif ($this->container->getParameter('kernel.trust_proxy_headers')) { - Request::trustProxyData(); // @deprecated, to be removed in 2.3 + } + + if ($this->container->getParameter('kernel.http_method_override')) { + Request::enableHttpMethodParameterOverride(); } if ($trustedHosts = $this->container->getParameter('kernel.trusted_hosts')) { @@ -56,11 +59,13 @@ public function build(ContainerBuilder $container) { parent::build($container); + // we need to add the request scope as early as possible so that + // the compilation can find scope widening issues $container->addScope(new Scope('request')); $container->addCompilerPass(new RoutingResolverPass()); $container->addCompilerPass(new ProfilerPass()); - $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new TemplatingPass()); $container->addCompilerPass(new AddConstraintValidatorsPass()); $container->addCompilerPass(new AddValidatorInitializersPass()); @@ -71,6 +76,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new TranslationExtractorPass()); $container->addCompilerPass(new TranslationDumperPass()); $container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new SerializerPass()); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php deleted file mode 100644 index c9f346363ff75..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle; - -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel; - -/** - * This HttpKernel is used to manage scope changes of the DI container. - * - * @author Fabien Potencier - * @author Johannes M. Schmitt - * - * @deprecated This class is deprecated in 2.2 and will be removed in 2.3 - */ -class HttpKernel extends ContainerAwareHttpKernel -{ - /** - * Forwards the request to another controller. - * - * @param string $controller The controller name (a string like BlogBundle:Post:index) - * @param array $attributes An array of request attributes - * @param array $query An array of request query parameters - * - * @return Response A Response instance - * - * @deprecated in 2.2, will be removed in 2.3 - */ - public function forward($controller, array $attributes = array(), array $query = array()) - { - trigger_error('forward() is deprecated since version 2.2 and will be removed in 2.3.', E_USER_DEPRECATED); - - $attributes['_controller'] = $controller; - $subRequest = $this->container->get('request')->duplicate($query, null, $attributes); - - return $this->handle($subRequest, HttpKernelInterface::SUB_REQUEST); - } - - /** - * Renders a Controller and returns the Response content. - * - * Note that this method generates an esi:include tag only when both the standalone - * option is set to true and the request has ESI capability (@see Symfony\Component\HttpKernel\HttpCache\ESI). - * - * Available options: - * - * * ignore_errors: true to return an empty string in case of an error - * * alt: an alternative URI to execute in case of an error - * * standalone: whether to generate an esi:include tag or not when ESI is supported - * * comment: a comment to add when returning an esi:include tag - * - * @param string $uri A URI - * @param array $options An array of options - * - * @return string The Response content - * - * @throws \RuntimeException - * @throws \Exception - * - * @deprecated in 2.2, will be removed in 2.3 (use Symfony\Component\HttpKernel\Fragment\FragmentHandler::render() instead) - */ - public function render($uri, array $options = array()) - { - trigger_error('render() is deprecated since version 2.2 and will be removed in 2.3. Use Symfony\Component\HttpKernel\Fragment\FragmentHandler::render() instead.', E_USER_DEPRECATED); - - $options = $this->renderer->fixOptions($options); - - $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; - unset($options['strategy']); - - $this->container->get('fragment.handler')->render($uri, $strategy, $options); - } -} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index 7d10cc70b9ee5..e7d1c3c7d47b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -9,7 +9,6 @@ Symfony\Component\Stopwatch\Stopwatch %kernel.cache_dir%/%kernel.container_class%.xml Symfony\Component\HttpKernel\Controller\TraceableControllerResolver - Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener @@ -28,9 +27,10 @@ - + + deprecation diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml new file mode 100644 index 0000000000000..36872ad529ca3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml @@ -0,0 +1,19 @@ + + + + + + Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener + + + + + + + emergency + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index d614e4dc50757..569100fce75fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -58,9 +58,6 @@ - - - @@ -140,6 +137,18 @@ + + + + + + + + + + + + @@ -154,5 +163,8 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 72442bcbba682..57cad204aa386 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -19,6 +19,8 @@ %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% + + %validator.translation_domain% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml index cbe3db3257b6e..595db6d2741c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml @@ -14,14 +14,15 @@ - %kernel.debug% + + %fragment.path% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router.php deleted file mode 100644 index e55c81c844280..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/* - * This file implements rewrite rules for PHP built-in web server. - * - * See: http://www.php.net/manual/en/features.commandline.webserver.php - * - * If you have custom directory layout, then you have to write your own router - * and pass it as a value to 'router' option of server:run command. - * - * @author: Michał Pipa - * @author: Albert Jessurum - */ - -if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) { - return false; -} - -$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'app_dev.php'; - -require 'app_dev.php'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php new file mode 100644 index 0000000000000..64c90b4bd20c4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file implements rewrite rules for PHP built-in web server. + * + * See: http://www.php.net/manual/en/features.commandline.webserver.php + * + * If you have custom directory layout, then you have to write your own router + * and pass it as a value to 'router' option of server:run command. + * + * @author: Michał Pipa + * @author: Albert Jessurum + */ + +if (is_file($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'])) { + return false; +} + +$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app_dev.php'; + +require 'app_dev.php'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.php new file mode 100644 index 0000000000000..4278b4bba15be --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file implements rewrite rules for PHP built-in web server. + * + * See: http://www.php.net/manual/en/features.commandline.webserver.php + * + * If you have custom directory layout, then you have to write your own router + * and pass it as a value to 'router' option of server:run command. + * + * @author: Michał Pipa + * @author: Albert Jessurum + */ + +if (is_file($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'])) { + return false; +} + +$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app.php'; + +require 'app.php'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 6d6671b7ae33f..9e21db4519151 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -94,6 +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 6672837bba1b8..4b235051f82d5 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 @@ -22,9 +22,7 @@ - - - + @@ -55,6 +53,7 @@ + @@ -87,14 +86,6 @@ - - - - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml new file mode 100644 index 0000000000000..491ccbcf2cf53 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml @@ -0,0 +1,26 @@ + + + + + + Symfony\Component\Serializer\Serializer + Symfony\Component\Serializer\Encoder\XmlEncoder + Symfony\Component\Serializer\Encoder\JsonEncoder + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index fbddc0e07fbfd..674e28f1c98a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -6,7 +6,7 @@ Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher - Symfony\Bundle\FrameworkBundle\HttpKernel + Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel Symfony\Component\Filesystem\Filesystem Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer @@ -40,7 +40,7 @@ This service definition only defines the scope of the request. It is used to check references scope. --> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index a89cf9a1737a6..de45365751be9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -9,6 +9,7 @@ Symfony\Component\HttpFoundation\Session\Flash\FlashBag Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag 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\Bundle\FrameworkBundle\EventListener\SessionListener @@ -26,6 +27,10 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml index 0d3cebebea9a7..23da774223b45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_debug.xml @@ -6,6 +6,7 @@ Symfony\Bundle\FrameworkBundle\Templating\Debugger + Symfony\Bundle\FrameworkBundle\Templating\TimedPhpEngine @@ -13,5 +14,12 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index d34ecdf3c3727..177821a5afb24 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -38,6 +38,7 @@ %kernel.default_locale% + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/exception.css b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/exception.css index bfd9094f30483..7426d44f885d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/exception.css +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/exception.css @@ -106,3 +106,8 @@ color: #000; font-size: 12px; } +.sf-reset #traces-text pre { + white-space: pre; + font-size: 12px; + font-family: monospace; +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/structure.css b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/structure.css index b780082518bdf..00b948f2289af 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/structure.css +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/structure.css @@ -66,7 +66,3 @@ pre { white-space: normal; font-family: Arial, Helvetica, sans-serif; } -pre.xdebug-var-dump{ - white-space: pre; - font-family: monospace; -} 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 new file mode 100644 index 0000000000000..63d16bd357dc6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php @@ -0,0 +1,6 @@ +id="escape($id) ?>" +name="escape($full_name) ?>" +disabled="disabled" + $v): ?> + escape($k), $view->escape($v)) ?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_label.html.php new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_row.html.php new file mode 100644 index 0000000000000..b52e92984533d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_row.html.php @@ -0,0 +1,3 @@ +
+ widget($form) ?> +
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php new file mode 100644 index 0000000000000..64d44666888ba --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php @@ -0,0 +1,2 @@ +humanize($name); } ?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php deleted file mode 100644 index 2e107a7b26be0..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_enctype.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_enctype') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php deleted file mode 100644 index ffed7cf43cad0..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_errors.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_errors') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php deleted file mode 100644 index 0b59dfb301bc5..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_label.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_label') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php deleted file mode 100644 index ee1bc31333ed1..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rest.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_rest') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php deleted file mode 100644 index 971d8ac5a9d76..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_row.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_row') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php deleted file mode 100644 index d4af23d712320..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_rows.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_rows') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php deleted file mode 100644 index 91e5ef1e1c144..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/field_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple') ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form.html.php new file mode 100644 index 0000000000000..fb789faff723a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form.html.php @@ -0,0 +1,3 @@ +start($form) ?> + widget($form) ?> +end($form) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_end.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_end.html.php new file mode 100644 index 0000000000000..fe6843905cee5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_end.html.php @@ -0,0 +1,4 @@ + +rest($form) ?> + + 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 new file mode 100644 index 0000000000000..9c3af35ffb9aa --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_start.html.php @@ -0,0 +1,6 @@ + + +
$v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?> enctype="multipart/form-data"> + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php new file mode 100644 index 0000000000000..1575e8292801e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php @@ -0,0 +1 @@ +block($form, 'button_widget', array('type' => isset($type) ? $type : 'reset')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php new file mode 100644 index 0000000000000..d42bb2a78ffe9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php @@ -0,0 +1 @@ +block($form, 'button_widget', array('type' => isset($type) ? $type : 'submit')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/button_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/button_row.html.php new file mode 100644 index 0000000000000..ef4d22b975081 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/button_row.html.php @@ -0,0 +1,6 @@ + + + + widget($form) ?> + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php index 000bfe545d2e2..253d348547491 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php @@ -46,8 +46,6 @@ public function __construct(FragmentHandler $handler) */ public function render($uri, array $options = array()) { - $options = $this->handler->fixOptions($options); - $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline'; unset($options['strategy']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php index 9ebd882a063b3..8ae77f50ba276 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @@ -57,19 +57,87 @@ public function setTheme(FormView $view, $themes) $this->renderer->setTheme($view, $themes); } + /** + * Renders the HTML for a form. + * + * Example usage: + * + * form($form) ?> + * + * You can pass options during the call: + * + * form($form, array('attr' => array('class' => 'foo'))) ?> + * + * form($form, array('separator' => '+++++')) ?> + * + * This method is mainly intended for prototyping purposes. If you want to + * control the layout of a form in a more fine-grained manner, you are + * advised to use the other helper methods for rendering the parts of the + * form individually. You can also create a custom form theme to adapt + * the look of the form. + * + * @param FormView $view The view for which to render the form + * @param array $variables Additional variables passed to the template + * + * @return string The HTML markup + */ + public function form(FormView $view, array $variables = array()) + { + return $this->renderer->renderBlock($view, 'form', $variables); + } + + /** + * Renders the form start tag. + * + * Example usage templates: + * + * start($form) ?>> + * + * @param FormView $view The view for which to render the start tag + * @param array $variables Additional variables passed to the template + * + * @return string The HTML markup + */ + public function start(FormView $view, array $variables = array()) + { + return $this->renderer->renderBlock($view, 'form_start', $variables); + } + + /** + * Renders the form end tag. + * + * Example usage templates: + * + * end($form) ?>> + * + * @param FormView $view The view for which to render the end tag + * @param array $variables Additional variables passed to the template + * + * @return string The HTML markup + */ + public function end(FormView $view, array $variables = array()) + { + return $this->renderer->renderBlock($view, 'form_end', $variables); + } + /** * Renders the HTML enctype in the form tag, if necessary. * * Example usage templates: * - * enctype() ?>> + * enctype($form) ?>> * * @param FormView $view The view for which to render the encoding type * * @return string The HTML markup + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use + * {@link start} instead. */ public function enctype(FormView $view) { + // Uncomment this as soon as the deprecation note should be shown + // trigger_error('The form helper $view[\'form\']->enctype() is deprecated since version 2.3 and will be removed in 3.0. Use $view[\'form\']->start() instead.', E_USER_DEPRECATED); return $this->renderer->searchAndRenderBlock($view, 'enctype'); } @@ -78,13 +146,13 @@ public function enctype(FormView $view) * * Example usage: * - * widget() ?> + * widget($form) ?> * * You can pass options during the call: * - * widget(array('attr' => array('class' => 'foo'))) ?> + * widget($form, array('attr' => array('class' => 'foo'))) ?> * - * widget(array('separator' => '+++++')) ?> + * widget($form, array('separator' => '+++++')) ?> * * @param FormView $view The view for which to render the widget * @param array $variables Additional variables passed to the template diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php index 4eeda1d8573df..ef2ae0800886a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php @@ -56,19 +56,11 @@ public function parse($name) throw new \RuntimeException(sprintf('Template name "%s" contains invalid characters.', $name)); } - $parts = explode(':', $name); - if (3 !== count($parts)) { + if (!preg_match('/^([^:]*):([^:]*):(.+)\.([^\.]+)\.([^\.]+)$/', $name, $matches)) { throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.format.engine").', $name)); } - $elements = explode('.', $parts[2]); - if (3 > count($elements)) { - throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.format.engine").', $name)); - } - $engine = array_pop($elements); - $format = array_pop($elements); - - $template = new TemplateReference($parts[0], $parts[1], implode('.', $elements), $format, $engine); + $template = new TemplateReference($matches[1], $matches[2], $matches[3], $matches[4], $matches[5]); if ($template->get('bundle')) { try { diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/TimedPhpEngine.php b/src/Symfony/Bundle/FrameworkBundle/Templating/TimedPhpEngine.php new file mode 100644 index 0000000000000..cdbdc6f2bb4a3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/TimedPhpEngine.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Templating; + +use Symfony\Bundle\FrameworkBundle\Templating\PhpEngine; +use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables; +use Symfony\Component\Templating\TemplateNameParserInterface; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Templating\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Times the time spent to render a template. + * + * @author Fabien Potencier + */ +class TimedPhpEngine extends PhpEngine +{ + protected $stopwatch; + + /** + * Constructor. + * + * @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance + * @param ContainerInterface $container A ContainerInterface instance + * @param LoaderInterface $loader A LoaderInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param GlobalVariables $globals A GlobalVariables instance + */ + public function __construct(TemplateNameParserInterface $parser, ContainerInterface $container, LoaderInterface $loader, Stopwatch $stopwatch, GlobalVariables $globals = null) + { + parent::__construct($parser, $container, $loader, $globals); + + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function render($name, array $parameters = array()) + { + $e = $this->stopwatch->start(sprintf('template.php (%s)', $name), 'template'); + + $ret = parent::render($name, $parameters); + + $e->stop(); + + return $ret; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php index 1427453b10650..42fcb6f597dd7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php @@ -15,12 +15,13 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Tester\ApplicationTester; class ApplicationTest extends TestCase { public function testBundleInterfaceImplementation() { - $bundle = $this->getMock("Symfony\Component\HttpKernel\Bundle\BundleInterface"); + $bundle = $this->getMock('Symfony\Component\HttpKernel\Bundle\BundleInterface'); $kernel = $this->getKernel(array($bundle)); @@ -30,7 +31,7 @@ public function testBundleInterfaceImplementation() public function testBundleCommandsAreRegistered() { - $bundle = $this->getMock("Symfony\Component\HttpKernel\Bundle\Bundle"); + $bundle = $this->getMock('Symfony\Component\HttpKernel\Bundle\Bundle'); $bundle->expects($this->once())->method('registerCommands'); $kernel = $this->getKernel(array($bundle)); @@ -39,14 +40,52 @@ public function testBundleCommandsAreRegistered() $application->doRun(new ArrayInput(array('list')), new NullOutput()); } + public function testBundleCommandsHaveRightContainer() + { + $command = $this->getMockForAbstractClass('Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand', array('foo'), '', true, true, true, array('setContainer')); + $command->setCode(function () {}); + $command->expects($this->exactly(2))->method('setContainer'); + + $application = new Application($this->getKernel(array())); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add($command); + $tester = new ApplicationTester($application); + + // set container is called here + $tester->run(array('command' => 'foo')); + + // as the container might have change between two runs, setContainer must called again + $tester->run(array('command' => 'foo')); + } + private function getKernel(array $bundles) { - $kernel = $this->getMock("Symfony\Component\HttpKernel\KernelInterface"); + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $dispatcher + ->expects($this->atLeastOnce()) + ->method('dispatch') + ; + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->atLeastOnce()) + ->method('get') + ->with($this->equalTo('event_dispatcher')) + ->will($this->returnValue($dispatcher)) + ; + + $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); $kernel ->expects($this->any()) ->method('getBundles') ->will($this->returnValue($bundles)) ; + $kernel + ->expects($this->any()) + ->method('getContainer') + ->will($this->returnValue($container)) + ; return $kernel; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php index 3f1dc249d278f..b18ade879392f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php @@ -55,6 +55,35 @@ public function testParse() } } + public function testBuild() + { + $parser = $this->createParser(); + + $this->assertEquals('FooBundle:Default:index', $parser->build('TestBundle\FooBundle\Controller\DefaultController::indexAction'), '->parse() converts a class::method string to a short a:b:c notation string'); + $this->assertEquals('FooBundle:Sub\Default:index', $parser->build('TestBundle\FooBundle\Controller\Sub\DefaultController::indexAction'), '->parse() converts a class::method string to a short a:b:c notation string'); + + try { + $parser->build('TestBundle\FooBundle\Controller\DefaultController::index'); + $this->fail('->parse() throws an \InvalidArgumentException if the controller is not an aController::cAction string'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->parse() throws an \InvalidArgumentException if the controller is not an aController::cAction string'); + } + + try { + $parser->build('TestBundle\FooBundle\Controller\Default::indexAction'); + $this->fail('->parse() throws an \InvalidArgumentException if the controller is not an aController::cAction string'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->parse() throws an \InvalidArgumentException if the controller is not an aController::cAction string'); + } + + try { + $parser->build('Foo\Controller\DefaultController::indexAction'); + $this->fail('->parse() throws an \InvalidArgumentException if the controller is not an aController::cAction string'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->parse() throws an \InvalidArgumentException if the controller is not an aController::cAction string'); + } + } + /** * @dataProvider getMissingControllersTest */ @@ -96,6 +125,18 @@ private function createParser() })) ; + $bundles = array( + 'SensioFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'), + 'SensioCmsFooBundle' => $this->getBundle('TestBundle\Sensio\Cms\FooBundle', 'SensioCmsFooBundle'), + 'FooBundle' => $this->getBundle('TestBundle\FooBundle', 'FooBundle'), + 'FabpotFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'), + ); + $kernel + ->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue($bundles)) + ; + return new ControllerNameParser($kernel); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php index 171116585bc7b..9baec8dd95b2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -24,16 +24,14 @@ class RedirectControllerTest extends TestCase { public function testEmptyRoute() { - $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); - + $request = new Request(); $controller = new RedirectController(); - $controller->setContainer($container); - $returnResponse = $controller->redirectAction('', true); + $returnResponse = $controller->redirectAction($request, '', true); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); $this->assertEquals(410, $returnResponse->getStatusCode()); - $returnResponse = $controller->redirectAction('', false); + $returnResponse = $controller->redirectAction($request, '', false); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); $this->assertEquals(404, $returnResponse->getStatusCode()); } @@ -41,13 +39,12 @@ public function testEmptyRoute() /** * @dataProvider provider */ - public function testRoute($permanent, $expectedCode) + public function testRoute($permanent, $ignoreAttributes, $expectedCode, $expectedAttributes) { $request = new Request(); $route = 'new-route'; $url = '/redirect-url'; - $params = array('additional-parameter' => 'value'); $attributes = array( 'route' => $route, 'permanent' => $permanent, @@ -55,9 +52,10 @@ public function testRoute($permanent, $expectedCode) '_route_params' => array( 'route' => $route, 'permanent' => $permanent, + 'additional-parameter' => 'value', + 'ignoreAttributes' => $ignoreAttributes ), ); - $attributes['_route_params'] = $attributes['_route_params'] + $params; $request->attributes = new ParameterBag($attributes); @@ -65,19 +63,13 @@ public function testRoute($permanent, $expectedCode) $router ->expects($this->once()) ->method('generate') - ->with($this->equalTo($route), $this->equalTo($params)) + ->with($this->equalTo($route), $this->equalTo($expectedAttributes)) ->will($this->returnValue($url)); $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); $container - ->expects($this->at(0)) - ->method('get') - ->with($this->equalTo('request')) - ->will($this->returnValue($request)); - - $container - ->expects($this->at(1)) + ->expects($this->once()) ->method('get') ->with($this->equalTo('router')) ->will($this->returnValue($router)); @@ -85,7 +77,7 @@ public function testRoute($permanent, $expectedCode) $controller = new RedirectController(); $controller->setContainer($container); - $returnResponse = $controller->redirectAction($route, $permanent); + $returnResponse = $controller->redirectAction($request, $route, $permanent, $ignoreAttributes); $this->assertRedirectUrl($returnResponse, $url); $this->assertEquals($expectedCode, $returnResponse->getStatusCode()); @@ -94,28 +86,32 @@ public function testRoute($permanent, $expectedCode) public function provider() { return array( - array(true, 301), - array(false, 302), + array(true, false, 301, array('additional-parameter' => 'value')), + array(false, false, 302, array('additional-parameter' => 'value')), + array(false, true, 302, array()), + array(false, array('additional-parameter'), 302, array()), ); } public function testEmptyPath() { + $request = new Request(); $controller = new RedirectController(); - $returnResponse = $controller->urlRedirectAction('', true); + $returnResponse = $controller->urlRedirectAction($request, '', true); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); $this->assertEquals(410, $returnResponse->getStatusCode()); - $returnResponse = $controller->urlRedirectAction('', false); + $returnResponse = $controller->urlRedirectAction($request, '', false); $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); $this->assertEquals(404, $returnResponse->getStatusCode()); } public function testFullURL() { + $request = new Request(); $controller = new RedirectController(); - $returnResponse = $controller->urlRedirectAction('http://foo.bar/'); + $returnResponse = $controller->urlRedirectAction($request, 'http://foo.bar/'); $this->assertRedirectUrl($returnResponse, 'http://foo.bar/'); $this->assertEquals(302, $returnResponse->getStatusCode()); @@ -131,14 +127,14 @@ public function testUrlRedirectDefaultPortParameters() $expectedUrl = "https://$host:$httpsPort$baseUrl$path"; $request = $this->createRequestObject('http', $host, $httpPort, $baseUrl); - $controller = $this->createRedirectController($request, null, $httpsPort); - $returnValue = $controller->urlRedirectAction($path, false, 'https'); + $controller = $this->createRedirectController(null, $httpsPort); + $returnValue = $controller->urlRedirectAction($request, $path, false, 'https'); $this->assertRedirectUrl($returnValue, $expectedUrl); $expectedUrl = "http://$host:$httpPort$baseUrl$path"; $request = $this->createRequestObject('https', $host, $httpPort, $baseUrl); - $controller = $this->createRedirectController($request, $httpPort); - $returnValue = $controller->urlRedirectAction($path, false, 'http'); + $controller = $this->createRedirectController($httpPort); + $returnValue = $controller->urlRedirectAction($request, $path, false, 'http'); $this->assertRedirectUrl($returnValue, $expectedUrl); } @@ -184,9 +180,9 @@ public function testUrlRedirect($scheme, $httpPort, $httpsPort, $requestScheme, $expectedUrl = "$scheme://$host$expectedPort$baseUrl$path"; $request = $this->createRequestObject($requestScheme, $host, $requestPort, $baseUrl); - $controller = $this->createRedirectController($request); + $controller = $this->createRedirectController(); - $returnValue = $controller->urlRedirectAction($path, false, $scheme, $httpPort, $httpsPort); + $returnValue = $controller->urlRedirectAction($request, $path, false, $scheme, $httpPort, $httpsPort); $this->assertRedirectUrl($returnValue, $expectedUrl); } @@ -213,14 +209,10 @@ private function createRequestObject($scheme, $host, $port, $baseUrl) return $request; } - private function createRedirectController(Request $request, $httpPort = null, $httpsPort = null) + private function createRedirectController($httpPort = null, $httpsPort = null) { $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); - $container - ->expects($this->at(0)) - ->method('get') - ->with($this->equalTo('request')) - ->will($this->returnValue($request)); + if (null !== $httpPort) { $container ->expects($this->once()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/RegisterKernelListenersPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/RegisterKernelListenersPassTest.php deleted file mode 100644 index 670052fe1b3ad..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/RegisterKernelListenersPassTest.php +++ /dev/null @@ -1,89 +0,0 @@ - - * - * 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\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass; - -class RegisterKernelListenersPassTest extends \PHPUnit_Framework_TestCase -{ - /** - * Tests that event subscribers not implementing EventSubscriberInterface - * trigger an exception. - * - * @expectedException \InvalidArgumentException - */ - public function testEventSubscriberWithoutInterface() - { - // one service, not implementing any interface - $services = array( - 'my_event_subscriber' => array(0 => array()), - ); - - $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); - $definition->expects($this->atLeastOnce()) - ->method('getClass') - ->will($this->returnValue('stdClass')); - - $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); - $builder->expects($this->any()) - ->method('hasDefinition') - ->will($this->returnValue(true)); - - // We don't test kernel.event_listener here - $builder->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->onConsecutiveCalls(array(), $services)); - - $builder->expects($this->atLeastOnce()) - ->method('getDefinition') - ->will($this->returnValue($definition)); - - $registerListenersPass = new RegisterKernelListenersPass(); - $registerListenersPass->process($builder); - } - - public function testValidEventSubscriber() - { - $services = array( - 'my_event_subscriber' => array(0 => array()), - ); - - $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); - $definition->expects($this->atLeastOnce()) - ->method('getClass') - ->will($this->returnValue('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\SubscriberService')); - - $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); - $builder->expects($this->any()) - ->method('hasDefinition') - ->will($this->returnValue(true)); - - // We don't test kernel.event_listener here - $builder->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->onConsecutiveCalls(array(), $services)); - - $builder->expects($this->atLeastOnce()) - ->method('getDefinition') - ->will($this->returnValue($definition)); - - $registerListenersPass = new RegisterKernelListenersPass(); - $registerListenersPass->process($builder); - } -} - -class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface -{ - public static function getSubscribedEvents() {} -} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/SerializerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/SerializerPassTest.php new file mode 100644 index 0000000000000..5a048ed41153c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/SerializerPassTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; + +/** + * Tests for the SerializerPass class + * + * @author Javier Lopez + */ +class SerializerPassTest extends \PHPUnit_Framework_TestCase +{ + + public function testThrowExceptionWhenNoNormalizers() + { + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + + $container->expects($this->once()) + ->method('hasDefinition') + ->with('serializer') + ->will($this->returnValue(true)); + + $container->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('serializer.normalizer') + ->will($this->returnValue(array())); + + $this->setExpectedException('RuntimeException'); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + } + + public function testThrowExceptionWhenNoEncoders() + { + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + + $container->expects($this->once()) + ->method('hasDefinition') + ->with('serializer') + ->will($this->returnValue(true)); + + $container->expects($this->any()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls( + array('n' => array('serializer.normalizer')), + array() + )); + + $container->expects($this->once()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $this->setExpectedException('RuntimeException'); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + } + + public function testServicesAreOrderedAccordingToPriority() + { + $services = array( + 'n3' => array('tag' => array()), + 'n1' => array('tag' => array('priority' => 200)), + 'n2' => array('tag' => array('priority' => 100)) + ); + + $expected = array( + new Reference('n1'), + new Reference('n2'), + new Reference('n3') + ); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $serializerPass = new SerializerPass(); + + $method = new \ReflectionMethod( + 'Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass', + 'findAndSortTaggedServices' + ); + $method->setAccessible(TRUE); + + $actual = $method->invoke($serializerPass, 'tag', $container); + + $this->assertEquals($expected, $actual); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 8f9ecf9cb4395..598622a1da7d1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -51,6 +51,8 @@ public function getTestValidTrustedProxiesData() array(null, array()), array(false, array()), array(array(), array()), + array(array('10.0.0.0/8'), array('10.0.0.0/8')), + array(array('::ffff:0:0/96'), array('::ffff:0:0/96')), ); } @@ -87,8 +89,7 @@ public function testInvalidValueTrustedProxies() protected static function getBundleDefaultConfig() { return array( - 'charset' => null, - 'trust_proxy_headers' => false, + 'http_method_override' => true, 'trusted_proxies' => array(), 'ide' => null, 'default_locale' => 'en', @@ -110,6 +111,7 @@ protected static function getBundleDefaultConfig() 'username' => '', 'password' => '', 'lifetime' => 86400, + 'collect' => true, ), 'translator' => array( 'enabled' => false, @@ -125,6 +127,9 @@ protected static function getBundleDefaultConfig() 'file_cache_dir' => '%kernel.cache_dir%/annotations', 'debug' => '%kernel.debug%', ), + 'serializer' => array( + 'enabled' => false + ) ); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/deprecated_merge_full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/deprecated_merge_full.php deleted file mode 100644 index b7a56d63fff48..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/deprecated_merge_full.php +++ /dev/null @@ -1,20 +0,0 @@ -loadFromExtension('framework', array( - 'secret' => 's3cr3t', - 'session' => array( - 'storage_id' => 'session.storage.native', - 'handler_id' => 'session.handler.native_file', - 'name' => '_SYMFONY', - 'lifetime' => 2012, - 'path' => '/sf2', - 'domain' => 'sf2.example.com', - 'secure' => false, - 'httponly' => false, - 'cookie_lifetime' => 86400, - 'cookie_path' => '/', - 'cookie_domain' => 'example.com', - 'cookie_secure' => true, - 'cookie_httponly' => true, - ), -)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/deprecated_merge_partial.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/deprecated_merge_partial.php deleted file mode 100644 index 5d56c1c01eb08..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/deprecated_merge_partial.php +++ /dev/null @@ -1,17 +0,0 @@ -loadFromExtension('framework', array( - 'secret' => 's3cr3t', - 'session' => array( - 'storage_id' => 'session.storage.native', - 'handler_id' => 'session.handler.native_file', - 'name' => '_SYMFONY', - 'lifetime' => 2012, - 'path' => '/sf2', - 'domain' => 'sf2.example.com', - 'secure' => false, - 'cookie_lifetime' => 86400, - 'cookie_path' => '/', - 'cookie_httponly' => true, - ), -)); 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 27988e2c2ef4c..c4eff9349228e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -4,7 +4,7 @@ 'secret' => 's3cr3t', 'default_locale' => 'fr', 'form' => null, - 'trust_proxy_headers' => true, + 'http_method_override' => false, 'trusted_proxies' => array('127.0.0.1', '10.0.0.1'), 'csrf_protection' => array( 'enabled' => true, @@ -22,18 +22,18 @@ 'type' => 'xml', ), 'session' => array( - 'storage_id' => 'session.storage.native', - 'handler_id' => 'session.handler.native_file', - 'name' => '_SYMFONY', - 'lifetime' => 86400, - 'path' => '/', - 'domain' => 'example.com', - 'secure' => true, - 'httponly' => true, - 'gc_maxlifetime' => 90000, - 'gc_divisor' => 108, - 'gc_probability' => 1, - 'save_path' => '/path/to/sessions', + 'storage_id' => 'session.storage.native', + 'handler_id' => 'session.handler.native_file', + 'name' => '_SYMFONY', + 'cookie_lifetime' => 86400, + 'cookie_path' => '/', + 'cookie_domain' => 'example.com', + 'cookie_secure' => true, + 'cookie_httponly' => true, + 'gc_maxlifetime' => 90000, + 'gc_divisor' => 108, + 'gc_probability' => 1, + 'save_path' => '/path/to/sessions', ), 'templating' => array( 'assets_version' => 'SomeVersionScheme', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php new file mode 100644 index 0000000000000..6615aa74ce558 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'profiler' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session.php new file mode 100644 index 0000000000000..104183764a4db --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'session' => array( + 'handler_id' => null, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/deprecated_merge_full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/deprecated_merge_full.xml deleted file mode 100644 index 2a3b6d6e41a2a..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/deprecated_merge_full.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/deprecated_merge_partial.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/deprecated_merge_partial.xml deleted file mode 100644 index c58dcbfcc793d..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/deprecated_merge_partial.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - 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 eacd416597baa..8fd3e9b6528a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -6,7 +6,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/profiler.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/profiler.xml new file mode 100644 index 0000000000000..f3b3095ccd951 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/profiler.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session.xml new file mode 100644 index 0000000000000..118a08c807eef --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/deprecated_merge_full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/deprecated_merge_full.yml deleted file mode 100644 index af6abaf2766d6..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/deprecated_merge_full.yml +++ /dev/null @@ -1,16 +0,0 @@ -framework: - secret: s3cr3t - session: - storage_id: session.storage.native - handler_id: session.handler.native_file - name: _SYMFONY - lifetime: 2012 - path: /sf2 - domain: sf2.example.com - secure: false - httponly: false - cookie_lifetime: 86400 - cookie_path: / - cookie_domain: example.com - cookie_secure: true - cookie_httponly: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/deprecated_merge_partial.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/deprecated_merge_partial.yml deleted file mode 100644 index 765719c521d9f..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/deprecated_merge_partial.yml +++ /dev/null @@ -1,14 +0,0 @@ -framework: - secret: s3cr3t - session: - storage_id: session.storage.native - handler_id: session.handler.native_file - name: _SYMFONY - lifetime: 2012 - path: /sf2 - domain: sf2.example.com - secure: false - httponly: false - cookie_lifetime: 86400 - cookie_path: / - cookie_httponly: true 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 9eddd249d5b97..d013063916c38 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -2,7 +2,7 @@ framework: secret: s3cr3t default_locale: fr form: ~ - trust_proxy_headers: true + http_method_override: false trusted_proxies: ['127.0.0.1', '10.0.0.1'] csrf_protection: enabled: true @@ -16,18 +16,18 @@ framework: resource: %kernel.root_dir%/config/routing.xml type: xml session: - storage_id: session.storage.native - handler_id: session.handler.native_file - name: _SYMFONY - lifetime: 86400 - path: / - domain: example.com - secure: true - httponly: true - gc_probability: 1 - gc_divisor: 108 - gc_maxlifetime: 90000 - save_path: /path/to/sessions + storage_id: session.storage.native + handler_id: session.handler.native_file + name: _SYMFONY + cookie_lifetime: 86400 + cookie_path: / + cookie_domain: example.com + cookie_secure: true + cookie_httponly: true + gc_probability: 1 + gc_divisor: 108 + gc_maxlifetime: 90000 + save_path: /path/to/sessions templating: assets_version: SomeVersionScheme assets_base_urls: http://cdn.example.com diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml new file mode 100644 index 0000000000000..9052a2bdfb0c8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml @@ -0,0 +1,3 @@ +framework: + profiler: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session.yml new file mode 100644 index 0000000000000..d91b0c3147dfd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session.yml @@ -0,0 +1,3 @@ +framework: + session: + handler_id: null diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 00c0098a0dda9..123f8e877fdbe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -37,10 +37,16 @@ public function testProxies() { $container = $this->createContainerFromFile('full'); - $this->assertTrue($container->getParameter('kernel.trust_proxy_headers')); $this->assertEquals(array('127.0.0.1', '10.0.0.1'), $container->getParameter('kernel.trusted_proxies')); } + public function testHttpMethodOverride() + { + $container = $this->createContainerFromFile('full'); + + $this->assertFalse($container->getParameter('kernel.http_method_override')); + } + public function testEsi() { $container = $this->createContainerFromFile('full'); @@ -48,17 +54,20 @@ public function testEsi() $this->assertTrue($container->hasDefinition('esi'), '->registerEsiConfiguration() loads esi.xml'); } - public function testProfiler() + public function testEnabledProfiler() { - $container = $this->createContainerFromFile('full'); + $container = $this->createContainerFromFile('profiler'); $this->assertTrue($container->hasDefinition('profiler'), '->registerProfilerConfiguration() loads profiling.xml'); $this->assertTrue($container->hasDefinition('data_collector.config'), '->registerProfilerConfiguration() loads collectors.xml'); - $this->assertTrue($container->getParameter('profiler_listener.only_exceptions')); - $this->assertEquals('%profiler_listener.only_exceptions%', $container->getDefinition('profiler_listener')->getArgument(2)); + } - $calls = $container->getDefinition('profiler')->getMethodCalls(); - $this->assertEquals('disable', $calls[0][0]); + public function testDisabledProfiler() + { + $container = $this->createContainerFromFile('full'); + + $this->assertFalse($container->hasDefinition('profiler'), '->registerProfilerConfiguration() does not load profiling.xml'); + $this->assertFalse($container->hasDefinition('data_collector.config'), '->registerProfilerConfiguration() does not load collectors.xml'); } public function testRouter() @@ -105,34 +114,13 @@ public function testSession() $this->assertEquals('/path/to/sessions', $container->getParameter('session.save_path')); } - public function testSessionDeprecatedMergeFull() + public function testNullSessionHandler() { - $container = $this->createContainerFromFile('deprecated_merge_full'); + $container = $this->createContainerFromFile('session'); $this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml'); - - $options = $container->getParameter('session.storage.options'); - $this->assertEquals('_SYMFONY', $options['name']); - $this->assertEquals(86400, $options['cookie_lifetime']); - $this->assertEquals('/', $options['cookie_path']); - $this->assertEquals('example.com', $options['cookie_domain']); - $this->assertTrue($options['cookie_secure']); - $this->assertTrue($options['cookie_httponly']); - } - - public function testSessionDeprecatedMergePartial() - { - $container = $this->createContainerFromFile('deprecated_merge_partial'); - - $this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml'); - - $options = $container->getParameter('session.storage.options'); - $this->assertEquals('_SYMFONY', $options['name']); - $this->assertEquals(86400, $options['cookie_lifetime']); - $this->assertEquals('/', $options['cookie_path']); - $this->assertEquals('sf2.example.com', $options['cookie_domain']); - $this->assertFalse($options['cookie_secure']); - $this->assertTrue($options['cookie_httponly']); + $this->assertNull($container->getDefinition('session.storage.native')->getArgument(1)); + $this->assertNull($container->getDefinition('session.storage.php_bridge')->getArgument(0)); } public function testTemplating() @@ -193,26 +181,28 @@ public function testTranslator() } } - $rootDirectory = str_replace('/', DIRECTORY_SEPARATOR, realpath(__DIR__.'/../../../../..').'/'); - $files = array_map(function($resource) use ($rootDirectory, $resources) { return str_replace($rootDirectory, '', realpath($resource[1])); }, $resources); + $files = array_map(function($resource) { return realpath($resource[1]); }, $resources); + $ref = new \ReflectionClass('Symfony\Component\Validator\Validator'); $this->assertContains( - str_replace('/', DIRECTORY_SEPARATOR, 'Symfony/Component/Validator/Resources/translations/validators.en.xlf'), + strtr(dirname($ref->getFileName()) . '/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR), $files, '->registerTranslatorConfiguration() finds Validator translation resources' ); + $ref = new \ReflectionClass('Symfony\Component\Form\Form'); $this->assertContains( - str_replace('/', DIRECTORY_SEPARATOR, 'Symfony/Component/Form/Resources/translations/validators.en.xlf'), + strtr(dirname($ref->getFileName()) . '/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR), $files, '->registerTranslatorConfiguration() finds Form translation resources' ); + $ref = new \ReflectionClass('Symfony\Component\Security\Core\SecurityContext'); $this->assertContains( - str_replace('/', DIRECTORY_SEPARATOR, 'Symfony/Component/Security/Resources/translations/security.en.xlf'), + strtr(dirname(dirname($ref->getFileName())) . '/Resources/translations/security.en.xlf', '/', DIRECTORY_SEPARATOR), $files, '->registerTranslatorConfiguration() finds Security translation resources' ); $calls = $container->getDefinition('translator.default')->getMethodCalls(); - $this->assertEquals('fr', $calls[0][1][0]); + $this->assertEquals(array('fr'), $calls[0][1][0]); } /** @@ -234,8 +224,9 @@ public function testValidation() $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(__DIR__.'/../../../../Component/Form/Resources/config/validation.xml'), + realpath(dirname($ref->getFileName()).'/Resources/config/validation.xml'), array_map('realpath', $xmlFiles), '->registerValidationConfiguration() adds Form validation.xml to XML loader' ); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SubRequestController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SubRequestController.php new file mode 100644 index 0000000000000..95f5dfa76fd7d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SubRequestController.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\DependencyInjection\ContainerAware; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +class SubRequestController extends ContainerAware +{ + public function indexAction() + { + $handler = $this->container->get('fragment.handler'); + + $errorUrl = $this->generateUrl('subrequest_fragment_error', array('_locale' => 'fr', '_format' => 'json')); + $altUrl = $this->generateUrl('subrequest_fragment', array('_locale' => 'fr', '_format' => 'json')); + + // simulates a failure during the rendering of a fragment... + // should render fr/json + $content = $handler->render($errorUrl, 'inline', array('alt' => $altUrl)); + + // ...to check that the FragmentListener still references the right Request + // when rendering another fragment after the error occurred + // should render en/html instead of fr/json + $content .= $handler->render(new ControllerReference('TestBundle:SubRequest:fragment')); + + // forces the LocaleListener to set fr for the locale... + // should render fr/json + $content .= $handler->render($altUrl); + + // ...and check that after the rendering, the original Request is back + // and en is used as a locale + // should use en/html instead of fr/json + $content .= '--'.$this->generateUrl('subrequest_fragment'); + + // The RouterListener is also tested as if it does not keep the right + // Request in the context, a 301 would be generated + return new Response($content); + } + + public function fragmentAction(Request $request) + { + return new Response('--'.$request->getLocale().'/'.$request->getRequestFormat()); + } + + public function fragmentErrorAction() + { + throw new \RuntimeException('error'); + } + + protected function generateUrl($name, $arguments = array()) + { + return $this->container->get('router')->generate($name, $arguments); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml index ae72dc64ad457..57d83eb9c649c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml @@ -22,6 +22,21 @@ profiler: path: /profiler defaults: { _controller: TestBundle:Profiler:index } +subrequest_index: + path: /subrequest/{_locale}.{_format} + defaults: { _controller: TestBundle:SubRequest:index, _format: "html" } + schemes: [https] + +subrequest_fragment_error: + path: /subrequest/fragment/error/{_locale}.{_format} + defaults: { _controller: TestBundle:SubRequest:fragmentError, _format: "html" } + schemes: [http] + +subrequest_fragment: + path: /subrequest/fragment/{_locale}.{_format} + defaults: { _controller: TestBundle:SubRequest:fragment, _format: "html" } + schemes: [http] + fragment_home: path: /fragment_home defaults: { _controller: TestBundle:Fragment:index, _format: txt } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SubRequestsTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SubRequestsTest.php new file mode 100644 index 0000000000000..2676653b1ef6d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SubRequestsTest.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\Bundle\FrameworkBundle\Tests\Functional; + +/** + * @group functional + */ +class SubRequestsTest extends WebTestCase +{ + public function testStateAfterSubRequest() + { + $client = $this->createClient(array('test_case' => 'Session', 'root_config' => 'config.yml')); + $client->request('GET', 'https://localhost/subrequest/en'); + + $this->assertEquals('--fr/json--en/html--fr/json--http://localhost/subrequest/fragment/en', $client->getResponse()->getContent()); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php index 655c9c74e9f76..caf2fd8f12527 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php @@ -27,6 +27,11 @@ break; } + if (file_exists($dir.'/vendor/autoload.php')) { + require_once $dir.'/vendor/autoload.php'; + break; + } + $dir = dirname($dir); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Profiler/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Profiler/config.yml index 7c2f516966246..12ce67e548ea0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Profiler/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Profiler/config.yml @@ -3,4 +3,5 @@ imports: framework: profiler: - enabled: false + enabled: true + collect: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index 7382ab8782468..9a960e3bea010 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @@ -47,7 +47,7 @@ protected function getExtensions() // should be moved to the Form component once absolute file paths are supported // by the default name parser in the Templating component $reflClass = new \ReflectionClass('Symfony\Bundle\FrameworkBundle\FrameworkBundle'); - $root = realpath(dirname($reflClass->getFileName()) . '/Resources/views'); + $root = realpath(dirname($reflClass->getFileName()).'/Resources/views'); $rootTheme = realpath(__DIR__.'/Resources'); $templateNameParser = new StubTemplateNameParser($root, $rootTheme); $loader = new FilesystemLoader(array()); @@ -72,6 +72,11 @@ protected function tearDown() parent::tearDown(); } + protected function renderForm(FormView $view, array $vars = array()) + { + return (string) $this->engine->get('form')->form($view, $vars); + } + protected function renderEnctype(FormView $view) { return (string) $this->engine->get('form')->enctype($view); @@ -102,6 +107,16 @@ protected function renderRest(FormView $view, array $vars = array()) return (string) $this->engine->get('form')->rest($view, $vars); } + protected function renderStart(FormView $view, array $vars = array()) + { + return (string) $this->engine->get('form')->start($view, $vars); + } + + protected function renderEnd(FormView $view, array $vars = array()) + { + return (string) $this->engine->get('form')->end($view, $vars); + } + protected function setTheme(FormView $view, array $themes) { $this->engine->get('form')->setTheme($view, $themes); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php index f5f0fe0d6ba9d..96c56b65064e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @@ -47,7 +47,7 @@ protected function getExtensions() // should be moved to the Form component once absolute file paths are supported // by the default name parser in the Templating component $reflClass = new \ReflectionClass('Symfony\Bundle\FrameworkBundle\FrameworkBundle'); - $root = realpath(dirname($reflClass->getFileName()) . '/Resources/views'); + $root = realpath(dirname($reflClass->getFileName()).'/Resources/views'); $rootTheme = realpath(__DIR__.'/Resources'); $templateNameParser = new StubTemplateNameParser($root, $rootTheme); $loader = new FilesystemLoader(array()); @@ -73,6 +73,11 @@ protected function tearDown() parent::tearDown(); } + protected function renderForm(FormView $view, array $vars = array()) + { + return (string) $this->engine->get('form')->form($view, $vars); + } + protected function renderEnctype(FormView $view) { return (string) $this->engine->get('form')->enctype($view); @@ -103,6 +108,16 @@ protected function renderRest(FormView $view, array $vars = array()) return (string) $this->engine->get('form')->rest($view, $vars); } + protected function renderStart(FormView $view, array $vars = array()) + { + return (string) $this->engine->get('form')->start($view, $vars); + } + + protected function renderEnd(FormView $view, array $vars = array()) + { + return (string) $this->engine->get('form')->end($view, $vars); + } + protected function setTheme(FormView $view, array $themes) { $this->engine->get('form')->setTheme($view, $themes); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php new file mode 100644 index 0000000000000..76912e02865b1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/TimedPhpEngineTest.php @@ -0,0 +1,118 @@ + + * + * 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\TimedPhpEngine; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Templating\TemplateNameParser; +use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; + +class TimedPhpEngineTest extends TestCase +{ + public function testThatRenderLogsTime() + { + $container = $this->getContainer(); + $templateNameParser = $this->getTemplateNameParser(); + $globalVariables = $this->getGlobalVariables(); + $loader = $this->getLoader($this->getStorage()); + + $stopwatch = $this->getStopwatch(); + $stopwatchEvent = $this->getStopwatchEvent(); + + $stopwatch->expects($this->once()) + ->method('start') + ->with('template.php (index.php)', 'template') + ->will($this->returnValue($stopwatchEvent)); + + $stopwatchEvent->expects($this->once())->method('stop'); + + $engine = new TimedPhpEngine($templateNameParser, $container, $loader, $stopwatch, $globalVariables); + $engine->render('index.php'); + } + + /** + * @return \Symfony\Component\DependencyInjection\Container + */ + private function getContainer() + { + return $this->getMock('Symfony\Component\DependencyInjection\Container'); + } + + /** + * @return \Symfony\Component\Templating\TemplateNameParserInterface + */ + private function getTemplateNameParser() + { + $templateReference = $this->getMock('Symfony\Component\Templating\TemplateReferenceInterface'); + $templateNameParser = $this->getMock('Symfony\Component\Templating\TemplateNameParserInterface'); + $templateNameParser->expects($this->any()) + ->method('parse') + ->will($this->returnValue($templateReference)); + + return $templateNameParser; + } + + /** + * @return \Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables + */ + private function getGlobalVariables() + { + return $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return \Symfony\Component\Templating\Storage\StringStorage + */ + private function getStorage() + { + return $this->getMockBuilder('Symfony\Component\Templating\Storage\StringStorage') + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + } + + /** + * @param \Symfony\Component\Templating\Storage\StringStorage $storage + * + * @return \Symfony\Component\Templating\Loader\Loader + */ + private function getLoader($storage) + { + $loader = $this->getMockForAbstractClass('Symfony\Component\Templating\Loader\Loader'); + $loader->expects($this->once()) + ->method('load') + ->will($this->returnValue($storage)); + + return $loader; + } + + /** + * @return \Symfony\Component\Stopwatch\StopwatchEvent + */ + private function getStopwatchEvent() + { + return $this->getMockBuilder('Symfony\Component\Stopwatch\StopwatchEvent') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return \Symfony\Component\Stopwatch\Stopwatch + */ + private function getStopwatch() + { + return $this->getMock('Symfony\Component\Stopwatch\Stopwatch'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 99bfc175671c1..daa1eee38b641 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -17,26 +17,29 @@ ], "require": { "php": ">=5.3.3", - "symfony/dependency-injection" : "~2.0", - "symfony/config" : ">=2.2,<2.3-dev", + "symfony/dependency-injection" : "~2.2", + "symfony/config" : "~2.2", "symfony/event-dispatcher": "~2.1", - "symfony/http-kernel": ">=2.2,<2.3-dev", - "symfony/filesystem": ">=2.1,<2.3-dev", - "symfony/routing": ">=2.2,<2.3-dev", - "symfony/stopwatch": ">=2.2,<2.3-dev", + "symfony/http-kernel": "~2.3", + "symfony/filesystem": "~2.3", + "symfony/routing": "~2.2", + "symfony/stopwatch": "~2.3", "symfony/templating": "~2.1", - "symfony/translation": ">=2.2,<2.3-dev", + "symfony/translation": "~2.3", "doctrine/common": "~2.2" }, "require-dev": { "symfony/finder": "~2.0", - "symfony/security": ">=2.2,<2.3-dev" + "symfony/security": "~2.3", + "symfony/form": "~2.3", + "symfony/class-loader": "~2.1", + "symfony/validator": "~2.1" }, "suggest": { - "symfony/console": "2.2.*", - "symfony/finder": "2.2.*", - "symfony/form": "2.2.*", - "symfony/validator": "2.2.*" + "symfony/console": "", + "symfony/finder": "", + "symfony/form": "", + "symfony/validator": "" }, "autoload": { "psr-0": { "Symfony\\Bundle\\FrameworkBundle\\": "" } @@ -45,7 +48,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist new file mode 100644 index 0000000000000..663faf41e838e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Resources + ./Tests + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/.gitignore b/src/Symfony/Bundle/SecurityBundle/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 6ab7b5b5f44aa..724254de78b4d 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,11 +1,16 @@ CHANGELOG ========= +2.3.0 +----- + + * allowed for multiple IP address in security access_control rules + 2.2.0 ----- -* Added PBKDF2 Password encoder -* Added BCrypt password encoder + * Added PBKDF2 Password encoder + * Added BCrypt password encoder 2.1.0 ----- diff --git a/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php index d879e83232268..66a2f0abc1a7f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $schema->addToSchema($connection->getSchemaManager()->createSchema()); } catch (SchemaException $e) { - $output->writeln("Aborting: " . $e->getMessage()); + $output->writeln("Aborting: ".$e->getMessage()); return 1; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index c20530396719f..2e1be8c6947d7 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -152,6 +152,7 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode) ->arrayNode('access_control') ->cannotBeOverwritten() ->prototype('array') + ->fixXmlConfig('ip') ->children() ->scalarNode('requires_channel')->defaultNull()->end() ->scalarNode('path') @@ -160,7 +161,10 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode) ->example('^/path to resource/') ->end() ->scalarNode('host')->defaultNull()->end() - ->scalarNode('ip')->defaultNull()->end() + ->arrayNode('ips') + ->beforeNormalization()->ifString()->then(function($v) { return array($v); })->end() + ->prototype('scalar')->end() + ->end() ->arrayNode('methods') ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() ->prototype('scalar')->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index 6d3e1911311c4..27f08b0a1f9d9 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -29,6 +29,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface protected $options = array( 'check_path' => '/login_check', 'use_forward' => false, + 'require_previous_session' => true, ); protected $defaultSuccessHandlerOptions = array( diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 97b9dbe59e0d2..abf1b80efcb20 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -184,7 +184,7 @@ private function createAuthorization($config, ContainerBuilder $container) $access['path'], $access['host'], $access['methods'], - $access['ip'] + $access['ips'] ); $container->getDefinition('security.access_map') @@ -451,42 +451,33 @@ private function createEncoder($config, ContainerBuilder $container) // pbkdf2 encoder if ('pbkdf2' === $config['algorithm']) { - $arguments = array( - $config['hash_algorithm'], - $config['encode_as_base64'], - $config['iterations'], - $config['key_length'], - ); - return array( - 'class' => new Parameter('security.encoder.pbkdf2.class'), - 'arguments' => $arguments, + 'class' => new Parameter('security.encoder.pbkdf2.class'), + 'arguments' => array( + $config['hash_algorithm'], + $config['encode_as_base64'], + $config['iterations'], + $config['key_length'], + ), ); } // bcrypt encoder if ('bcrypt' === $config['algorithm']) { - $arguments = array( - new Reference('security.secure_random'), - $config['cost'], - ); - return array( - 'class' => new Parameter('security.encoder.bcrypt.class'), - 'arguments' => $arguments, + 'class' => new Parameter('security.encoder.bcrypt.class'), + 'arguments' => array($config['cost']), ); } // message digest encoder - $arguments = array( - $config['algorithm'], - $config['encode_as_base64'], - $config['iterations'], - ); - return array( - 'class' => new Parameter('security.encoder.digest.class'), - 'arguments' => $arguments, + 'class' => new Parameter('security.encoder.digest.class'), + 'arguments' => array( + $config['algorithm'], + $config['encode_as_base64'], + $config['iterations'], + ), ); } diff --git a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php index 59f7182bc3e84..c7135f54e1f70 100644 --- a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php +++ b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php @@ -101,10 +101,10 @@ private function generateLogoutUrl($key, $referenceType) if ('/' === $logoutPath[0]) { $request = $this->container->get('request'); - $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBasePath() . $logoutPath; + $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBasePath().$logoutPath; if (!empty($parameters)) { - $url .= '?' . http_build_query($parameters); + $url .= '?'.http_build_query($parameters); } } else { $url = $this->router->generate($logoutPath, $parameters, $referenceType); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 2f5a884b8903a..7060b05c04cf6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -160,10 +160,7 @@ public function testEncoders() ), 'JMS\FooBundle\Entity\User6' => array( 'class' => new Parameter('security.encoder.bcrypt.class'), - 'arguments' => array( - new Reference('security.secure_random'), - 15, - ) + 'arguments' => array(15), ), )), $container->getDefinition('security.encoder_factory.generic')->getArguments()); } 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 13788e1f96fc2..535df3576c2fb 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 @@ -25,6 +25,12 @@ form_login_redirect_to_protected_resource_after_login: highly_protected_resource: path: /highly_protected_resource +secured-by-one-ip: + path: /secured-by-one-ip + +secured-by-two-ips: + path: /secured-by-two-ips + form_logout: path: /logout_path diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php index 3ae81bc4f2f83..bb16373c1bbf6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php @@ -63,6 +63,46 @@ public function testRoutingErrorIsNotExposedForProtectedResourceWhenLoggedInWith $this->assertNotEquals(404, $client->getResponse()->getStatusCode()); } + /** + * @dataProvider getConfigs + * @group ip_whitelist + */ + public function testSecurityConfigurationForSingleIPAddress($config) + { + $allowedClient = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array("REMOTE_ADDR" => "10.10.10.10")); + $barredClient = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array("REMOTE_ADDR" => "10.10.20.10")); + + $this->assertAllowed($allowedClient, '/secured-by-one-ip'); + $this->assertRestricted($barredClient, '/secured-by-one-ip'); + } + + /** + * @dataProvider getConfigs + * @group ip_whitelist + */ + public function testSecurityConfigurationForMultipleIPAddresses($config) + { + $allowedClientA = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array("REMOTE_ADDR" => "1.1.1.1")); + $allowedClientB = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array("REMOTE_ADDR" => "2.2.2.2")); + $barredClient = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config), array("REMOTE_ADDR" => "192.168.1.1")); + + $this->assertAllowed($allowedClientA, '/secured-by-two-ips'); + $this->assertAllowed($allowedClientB, '/secured-by-two-ips'); + $this->assertRestricted($barredClient, '/secured-by-two-ips'); + } + + private function assertAllowed($client, $path) + { + $client->request('GET', $path); + $this->assertEquals(404, $client->getResponse()->getStatusCode()); + } + + private function assertRestricted($client, $path) + { + $client->request('GET', $path); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + } + public function getConfigs() { return array(array('config.yml'), array('routes_as_path.yml')); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php index 217e27bca1b03..8c0c33925ca95 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php @@ -23,7 +23,7 @@ public function testSwitchUser($originalUser, $targetUser, $expectedUser, $expec { $client = $this->createAuthenticatedClient($originalUser); - $client->request('GET', '/profile?_switch_user=' . $targetUser); + $client->request('GET', '/profile?_switch_user='.$targetUser); $this->assertEquals($expectedStatus, $client->getResponse()->getStatusCode()); $this->assertEquals($expectedUser, $client->getProfile()->getCollector('security')->getUser()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php index 3c78d10d33510..b4b906f172210 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php @@ -60,7 +60,7 @@ protected static function createKernel(array $options = array()) return new $class( $options['test_case'], isset($options['root_config']) ? $options['root_config'] : 'config.yml', - isset($options['environment']) ? $options['environment'] : 'securitybundletest' . strtolower($options['test_case']), + isset($options['environment']) ? $options['environment'] : 'securitybundletest'.strtolower($options['test_case']), isset($options['debug']) ? $options['debug'] : true ); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php index dbe618feacf03..0979db422909f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php @@ -27,6 +27,11 @@ break; } + if (file_exists($dir.'/vendor/autoload.php')) { + require_once $dir.'/vendor/autoload.php'; + break; + } + $dir = dirname($dir); } 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 6c12d1b07d5e6..58bd9f2d8af3c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml @@ -28,5 +28,7 @@ security: access_control: - { path: ^/unprotected_resource$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/secure-but-not-covered-by-access-control$, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { 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: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 3c36ac85263b8..776f590bc87c1 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -17,7 +17,15 @@ ], "require": { "php": ">=5.3.3", - "symfony/security": ">=2.2,<2.3-dev" + "symfony/security": "~2.2", + "symfony/http-kernel": "~2.2" + }, + "require-dev": { + "symfony/framework-bundle": "~2.2", + "symfony/twig-bundle": "~2.2", + "symfony/form": "~2.1", + "symfony/validator": "~2.2", + "symfony/yaml": "~2.0" }, "autoload": { "psr-0": { "Symfony\\Bundle\\SecurityBundle\\": "" } @@ -26,7 +34,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bundle/SecurityBundle/phpunit.xml.dist b/src/Symfony/Bundle/SecurityBundle/phpunit.xml.dist new file mode 100644 index 0000000000000..2cffc1e59e9e2 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./Resources + ./vendor + + + + diff --git a/src/Symfony/Bundle/TwigBundle/.gitignore b/src/Symfony/Bundle/TwigBundle/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index 8c03bdbbf1d4d..de36165b56c12 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.3.0 +----- + + * added option to configure a custom template escaping guesser (via `autoescape_service` and `autoescape_service_method`) + 2.2.0 ----- diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php new file mode 100644 index 0000000000000..069083d27f0f0 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Jean-François Simon + */ +class ExtensionPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if ($container->has('form.extension')) { + $container->getDefinition('twig.extension.form')->addTag('twig.extension'); + $reflClass = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension'); + $container->getDefinition('twig.loader.filesystem')->addMethodCall('addPath', array(dirname(dirname($reflClass->getFileName())).'/Resources/views/Form')); + } + + if ($container->has('translator')) { + $container->getDefinition('twig.extension.trans')->addTag('twig.extension'); + } + + if ($container->has('router')) { + $container->getDefinition('twig.extension.routing')->addTag('twig.extension'); + } + + if ($container->has('fragment.handler')) { + $container->getDefinition('twig.extension.httpkernel')->addTag('twig.extension'); + } + } +} diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index 5fd2002bed5f0..083eb0ce3563f 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -119,6 +119,8 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode) ->fixXmlConfig('path') ->children() ->scalarNode('autoescape')->end() + ->scalarNode('autoescape_service')->defaultNull()->end() + ->scalarNode('autoescape_service_method')->defaultNull()->end() ->scalarNode('base_template_class')->example('Twig_Template')->end() ->scalarNode('cache')->defaultValue('%kernel.cache_dir%/twig')->end() ->scalarNode('charset')->defaultValue('%kernel.charset%')->end() diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 5e056a85f4841..e8fec8ddc43dc 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -57,9 +57,6 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('twig.form.resources', $config['form']['resources']); - $reflClass = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension'); - $container->getDefinition('twig.loader.filesystem')->addMethodCall('addPath', array(dirname(dirname($reflClass->getFileName())).'/Resources/views/Form')); - $twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.filesystem'); // register user-configured paths @@ -104,8 +101,6 @@ public function load(array $configs, ContainerBuilder $container) $config['extensions'] ); - $container->setParameter('twig.options', $config); - if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); @@ -113,10 +108,16 @@ public function load(array $configs, ContainerBuilder $container) $container->setAlias('debug.templating.engine.twig', 'templating.engine.twig'); } - if (!isset($config['autoescape'])) { + if (isset($config['autoescape_service']) && isset($config['autoescape_service_method'])) { + $container->findDefinition('templating.engine.twig')->addMethodCall('setDefaultEscapingStrategy', array(array(new Reference($config['autoescape_service']), $config['autoescape_service_method']))); + + unset($config['autoescape_service'], $config['autoescape_service_method']); + } elseif (!isset($config['autoescape'])) { $container->findDefinition('templating.engine.twig')->addMethodCall('setDefaultEscapingStrategy', array(array(new Reference('templating.engine.twig'), 'guessDefaultEscapingStrategy'))); } + $container->setParameter('twig.options', $config); + $this->addClassesToCompile(array( 'Twig_Environment', 'Twig_Extension', diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd b/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd index 3706f1480f9ca..a75f645a4e750 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/schema/twig-1.0.xsd @@ -16,6 +16,8 @@ + + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 6dbc001c0b8fe..971f4f17ebefd 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -58,7 +58,6 @@ - @@ -80,7 +79,6 @@ - @@ -89,12 +87,11 @@ - + - diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig index 5b44b8121599b..f09ffb3c658de 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig @@ -47,8 +47,8 @@

Logs  - - - + + -

{% endspaceless %} @@ -74,8 +74,8 @@

Content of the Output  - - + + + +

{% endspaceless %} @@ -88,6 +88,8 @@ {% endif %} +{% include 'TwigBundle:Exception:traces_text.html.twig' with { 'exception': exception } only %} + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.html.twig index d4a11e1e50405..d00a376a4589e 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.html.twig @@ -12,8 +12,8 @@ in {{ trace.file|format_file(trace.line) }}  {% spaceless %} - - - + + - + + {% endspaceless %}
diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig index 1b3b77dc54243..ff20469bdbee8 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig @@ -1,8 +1,8 @@ {% if trace.function %} - at {{ trace.class ~ trace.type ~ trace.function }}({{ trace.args|format_args_as_text }}) + at {{ trace.class ~ trace.type ~ trace.function }}({{ trace.args|format_args_as_text }}) {% else %} - at n/a + at n/a {% endif %} {% if trace.file is defined and trace.line is defined %} - in {{ trace.file }} line {{ trace.line }} + in {{ trace.file }} line {{ trace.line }} {% endif %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig index e0f1a0db1325b..cf49082cf4ecf 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.html.twig @@ -5,8 +5,8 @@ {{ exception.class|abbr_class }}: {{ exception.message|nl2br|format_file_from_text }}  {% spaceless %} - - - + + - + + {% endspaceless %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces_text.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces_text.html.twig new file mode 100644 index 0000000000000..3ea3d7bba8ccf --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces_text.html.twig @@ -0,0 +1,18 @@ +
+

+ Stack Trace (Plain Text)  + {% spaceless %} + + + + + + {% endspaceless %} +

+ + +
diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/customTemplateEscapingGuesser.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/customTemplateEscapingGuesser.php new file mode 100644 index 0000000000000..9e1d40505dace --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/customTemplateEscapingGuesser.php @@ -0,0 +1,6 @@ +loadFromExtension('twig', array( + 'autoescape_service' => 'my_project.some_bundle.template_escaping_guesser', + 'autoescape_service_method' => 'guess', +)); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/empty.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/empty.php new file mode 100644 index 0000000000000..efd2df5f47918 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/empty.php @@ -0,0 +1,3 @@ +loadFromExtension('twig', array()); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/customTemplateEscapingGuesser.xml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/customTemplateEscapingGuesser.xml new file mode 100644 index 0000000000000..fa28361cc8af0 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/customTemplateEscapingGuesser.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/empty.xml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/empty.xml new file mode 100644 index 0000000000000..771e382e47002 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/empty.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/customTemplateEscapingGuesser.yml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/customTemplateEscapingGuesser.yml new file mode 100644 index 0000000000000..eb26e7165bb09 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/customTemplateEscapingGuesser.yml @@ -0,0 +1,3 @@ +twig: + autoescape_service: my_project.some_bundle.template_escaping_guesser + autoescape_service_method: guess diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/empty.yml b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/empty.yml new file mode 100644 index 0000000000000..a472b2698e5cd --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/empty.yml @@ -0,0 +1 @@ +twig: diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index e778a1621212d..225ecfd9bee2b 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -83,6 +83,32 @@ public function testLoadFullConfiguration($format) $this->assertTrue($options['strict_variables'], '->load() sets the strict_variables option'); } + /** + * @dataProvider getFormats + */ + public function testLoadCustomTemplateEscapingGuesserConfiguration($format) + { + $container = $this->createContainer(); + $container->registerExtension(new TwigExtension()); + $this->loadFromFile($container, 'customTemplateEscapingGuesser', $format); + $this->compileContainer($container); + + $this->assertTemplateEscapingGuesserDefinition($container, 'my_project.some_bundle.template_escaping_guesser', 'guess'); + } + + /** + * @dataProvider getFormats + */ + public function testLoadDefaultTemplateEscapingGuesserConfiguration($format) + { + $container = $this->createContainer(); + $container->registerExtension(new TwigExtension()); + $this->loadFromFile($container, 'empty', $format); + $this->compileContainer($container); + + $this->assertTemplateEscapingGuesserDefinition($container, 'templating.engine.twig', 'guessDefaultEscapingStrategy'); + } + public function testGlobalsWithDifferentTypesAndValues() { $globals = array( @@ -184,9 +210,23 @@ private function loadFromFile(ContainerBuilder $container, $file, $format) $loader = new YamlFileLoader($container, $locator); break; default: - throw new \InvalidArgumentException('Unsupported format: '.$format); + throw new \InvalidArgumentException(sprintf('Unsupported format: %s', $format)); } $loader->load($file.'.'.$format); } + + private function assertTemplateEscapingGuesserDefinition(ContainerBuilder $container, $serviceId, $serviceMethod) + { + $def = $container->getDefinition('templating.engine.twig'); + + $this->assertCount(1, $def->getMethodCalls()); + + foreach ($def->getMethodCalls() as $call) { + if ('setDefaultEscapingStrategy' === $call[0]) { + $this->assertSame($serviceId, (string) $call[1][0][0]); + $this->assertSame($serviceMethod, $call[1][0][1]); + } + } + } } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/TokenParser/RenderTokenParserTest.php b/src/Symfony/Bundle/TwigBundle/Tests/TokenParser/RenderTokenParserTest.php index cec9d47d6b467..9823a98fcafcc 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/TokenParser/RenderTokenParserTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/TokenParser/RenderTokenParserTest.php @@ -54,29 +54,6 @@ public function getTestsForRender() 'render' ) ), - // deprecated in 2.2 - array( - '{% render "foo" with {foo: 2} %}', - new RenderNode( - new \Twig_Node_Expression_Constant('foo', 1), - new \Twig_Node_Expression_Array(array(), 1), - 1, - 'render' - ) - ), - // deprecated in 2.2 - array( - '{% render "foo" with {foo: 2}, {foo: 1} %}', - new RenderNode( - new \Twig_Node_Expression_Constant('foo', 1), - new \Twig_Node_Expression_Array(array( - new \Twig_Node_Expression_Constant('foo', 1), - new \Twig_Node_Expression_Constant('1', 1), - ), 1), - 1, - 'render' - ) - ), ); } } diff --git a/src/Symfony/Bundle/TwigBundle/TokenParser/RenderTokenParser.php b/src/Symfony/Bundle/TwigBundle/TokenParser/RenderTokenParser.php index 884c5d0f4b434..fc53be4cbe051 100644 --- a/src/Symfony/Bundle/TwigBundle/TokenParser/RenderTokenParser.php +++ b/src/Symfony/Bundle/TwigBundle/TokenParser/RenderTokenParser.php @@ -31,13 +31,6 @@ public function parse(\Twig_Token $token) { $expr = $this->parser->getExpressionParser()->parseExpression(); - // attributes (not used anymore, kept for BC reasons) - // @deprecated in 2.2 and will be removed in 2.3 - if ($this->parser->getStream()->test(\Twig_Token::NAME_TYPE, 'with')) { - $this->parser->getStream()->next(); - $this->parser->getExpressionParser()->parseExpression(); - } - // options if ($this->parser->getStream()->test(\Twig_Token::PUNCTUATION_TYPE, ',')) { $this->parser->getStream()->next(); diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index 88a172caab0b0..7b4a2053f0f16 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -16,6 +16,7 @@ use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExceptionListenerPass; +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExtensionPass; /** * Bundle. @@ -28,6 +29,7 @@ public function build(ContainerBuilder $container) { parent::build($container); + $container->addCompilerPass(new ExtensionPass()); $container->addCompilerPass(new TwigEnvironmentPass()); $container->addCompilerPass(new TwigLoaderPass()); $container->addCompilerPass(new ExceptionListenerPass()); diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 8837fdf89e854..65933fcf89531 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -17,10 +17,13 @@ ], "require": { "php": ">=5.3.3", - "symfony/twig-bridge": "2.2.*" + "symfony/twig-bridge": "~2.2", + "symfony/http-kernel": "~2.1" }, "require-dev": { - "symfony/stopwatch": ">=2.2,<2.3-dev" + "symfony/stopwatch": "~2.2", + "symfony/dependency-injection": "~2.0", + "symfony/config": "~2.2" }, "autoload": { "psr-0": { "Symfony\\Bundle\\TwigBundle\\": "" } @@ -29,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "2.3-dev" } } } diff --git a/src/Symfony/Bundle/TwigBundle/phpunit.xml.dist b/src/Symfony/Bundle/TwigBundle/phpunit.xml.dist new file mode 100644 index 0000000000000..56e10bdad3fba --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./Resources + ./vendor + + + + diff --git a/src/Symfony/Bundle/WebProfilerBundle/.gitignore b/src/Symfony/Bundle/WebProfilerBundle/.gitignore index 44de97a36a6df..c49a5d8df5c65 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/.gitignore +++ b/src/Symfony/Bundle/WebProfilerBundle/.gitignore @@ -1,4 +1,3 @@ vendor/ composer.lock phpunit.xml - diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 38cac53a3132a..a27cdd8835826 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.3.0 +----- + + * draw retina canvas if devicePixelRatio is bigger than 1 + 2.1.0 ----- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php index fafd985ec3fe2..d05bcf8a0f11e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpKernel\Profiler\Profiler; use Symfony\Component\HttpKernel\Debug\ExceptionHandler; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpFoundation\Response; /** @@ -26,7 +27,7 @@ class ExceptionController protected $debug; protected $profiler; - public function __construct(Profiler $profiler, \Twig_Environment $twig, $debug) + public function __construct(Profiler $profiler = null, \Twig_Environment $twig, $debug) { $this->profiler = $profiler; $this->twig = $twig; @@ -42,6 +43,10 @@ public function __construct(Profiler $profiler, \Twig_Environment $twig, $debug) */ public function showAction($token) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); $exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException(); @@ -76,6 +81,10 @@ public function showAction($token) */ public function cssAction($token) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); $exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 4b0ba07ce9d34..bd60fb3751c9f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -43,7 +43,7 @@ class ProfilerController * @param array $templates The templates * @param string $toolbarPosition The toolbar position (top, bottom, normal, or null -- use the configuration) */ - public function __construct(UrlGeneratorInterface $generator, Profiler $profiler, \Twig_Environment $twig, array $templates, $toolbarPosition = 'normal') + public function __construct(UrlGeneratorInterface $generator, Profiler $profiler = null, \Twig_Environment $twig, array $templates, $toolbarPosition = 'normal') { $this->generator = $generator; $this->profiler = $profiler; @@ -59,6 +59,10 @@ public function __construct(UrlGeneratorInterface $generator, Profiler $profiler */ public function homeAction() { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); return new RedirectResponse($this->generator->generate('_profiler_search_results', array('token' => 'empty', 'limit' => 10)), 302, array('Content-Type' => 'text/html')); @@ -76,6 +80,10 @@ public function homeAction() */ public function panelAction(Request $request, $token) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); $panel = $request->query->get('panel', 'request'); @@ -112,6 +120,10 @@ public function panelAction(Request $request, $token) */ public function exportAction($token) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); if (!$profile = $this->profiler->loadProfile($token)) { @@ -131,6 +143,10 @@ public function exportAction($token) */ public function purgeAction() { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); $this->profiler->purge(); @@ -146,6 +162,10 @@ public function purgeAction() */ public function importAction(Request $request) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); $file = $request->files->get('file'); @@ -170,6 +190,10 @@ public function importAction(Request $request) */ public function infoAction($about) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); return new Response($this->twig->render('@WebProfiler/Profiler/info.html.twig', array( @@ -187,6 +211,10 @@ public function infoAction($about) */ public function toolbarAction(Request $request, $token) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $session = $request->getSession(); if (null !== $session && $session->getFlashBag() instanceof AutoExpireFlashBag) { @@ -234,6 +262,10 @@ public function toolbarAction(Request $request, $token) */ public function searchBarAction(Request $request) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); if (null === $session = $request->getSession()) { @@ -275,6 +307,10 @@ public function searchBarAction(Request $request) */ public function searchResultsAction(Request $request, $token) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); $profile = $this->profiler->loadProfile($token); @@ -309,6 +345,10 @@ public function searchResultsAction(Request $request, $token) */ public function searchAction(Request $request) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); $ip = preg_replace('/[^:\d\.]/', '', $request->query->get('ip')); @@ -353,6 +393,10 @@ public function searchAction(Request $request) */ public function phpinfoAction() { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); ob_start(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php index 1ff9961679ce7..591bedc13a699 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; +use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\HttpKernel\Profiler\Profiler; @@ -29,14 +30,14 @@ class RouterController private $matcher; private $routes; - public function __construct(Profiler $profiler, \Twig_Environment $twig, UrlMatcherInterface $matcher = null, $routes = null) + public function __construct(Profiler $profiler = null, \Twig_Environment $twig, UrlMatcherInterface $matcher = null, RouteCollection $routes = null) { $this->profiler = $profiler; $this->twig = $twig; $this->matcher = $matcher; $this->routes = $routes; - if (null === $this->routes && null !== $this->matcher && $this->matcher instanceof RouterInterface) { + if (null === $this->routes && $this->matcher instanceof RouterInterface) { $this->routes = $matcher->getRouteCollection(); } } @@ -50,6 +51,10 @@ public function __construct(Profiler $profiler, \Twig_Environment $twig, UrlMatc */ public function panelAction($token) { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + $this->profiler->disable(); if (null === $this->matcher || null === $this->routes) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php index 6ba5e1a563065..ece674c6c3cf2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php @@ -36,7 +36,6 @@ public function getConfigTreeBuilder() $rootNode ->children() - ->booleanNode('verbose')->defaultTrue()->info('DEPRECATED, it is not useful anymore and can be removed safely from your configuration')->end() ->booleanNode('toolbar')->defaultFalse()->end() ->scalarNode('position') ->defaultValue('bottom') diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml index 22d12409cdc5a..874ba8b216244 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml @@ -13,20 +13,20 @@ - + %data_collector.templates% %web_profiler.debug_toolbar.position% - + - + %kernel.debug% diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/schema/webprofiler-1.0.xsd b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/schema/webprofiler-1.0.xsd index 5c95e72e015fd..84cc8ae9a97f3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/schema/webprofiler-1.0.xsd +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/schema/webprofiler-1.0.xsd @@ -10,7 +10,6 @@ - diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig index d4fc494cc4edf..cdb7b8fbc38a9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -4,7 +4,7 @@ {# Symfony Logo #} {% set icon %} - Symfony + Symfony {% if collector.applicationname %} {{ collector.applicationname }} {{ collector.applicationversion }} @@ -32,7 +32,7 @@ {# PHP Information #} {% set icon %} - PHP + PHP {% endset %} {% set text %} @@ -57,27 +57,36 @@ {# Environment #} {% set debug_status_class %}sf-toolbar-status sf-toolbar-status-{{ collector.debug ? 'green' : 'red' }}{% endset %} {% set icon %} - Environment + Environment {{ token }} - - {{ collector.appname }}{{ collector.env }} - + {% if 'n/a' != collector.appname or 'n/a' != collector.env %} + + {{ collector.appname }} + {{ collector.env }} + + {% endif %} {% endset %} {% set text %} {% spaceless %} -
- Name - {{ collector.appname }} -
-
- Environment - {{ collector.env }} -
-
- Debug - {{ collector.debug ? 'en' : 'dis' }}abled -
+ {% if 'n/a' != collector.appname %} +
+ Name + {{ collector.appname }} +
+ {% endif %} + {% if 'n/a' != collector.env %} +
+ Environment + {{ collector.env }} +
+ {% endif %} + {% if 'n/a' != collector.debug %} +
+ Debug + {{ collector.debug ? 'en' : 'dis' }}abled +
+ {% endif %}
Token @@ -95,7 +104,7 @@ {% block menu %} - Configuration + Configuration Config {% endblock %} @@ -116,18 +125,24 @@ {{ collector.symfonyversion }} {% endif %} - - Application name - {{ collector.appname }} - - - Environment - {{ collector.env }} - - - Debug - {{ collector.debug ? 'enabled' : 'disabled' }} - + {% if 'n/a' != collector.appname %} + + Application name + {{ collector.appname }} + + {% endif %} + {% if 'n/a' != collector.env %} + + Environment + {{ collector.env }} + + {% endif %} + {% if 'n/a' != collector.debug %} + + Debug + {{ collector.debug ? 'enabled' : 'disabled' }} + + {% endif %}

PHP configuration

@@ -156,6 +171,10 @@ APC {{ collector.hasapc ? 'enabled' : 'disabled' }} + + Zend OPcache + {{ collector.haszendopcache ? 'enabled' : 'disabled' }} + EAccelerator {{ collector.haseaccelerator ? 'enabled' : 'disabled' }} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig index 50bd06f960699..6072ff69f2fe3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig @@ -4,7 +4,7 @@ {% block menu %} - Events + Events Events {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig index bfd9094f30483..1224081bd600a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig @@ -29,8 +29,6 @@ margin: 10px 0 20px; } .sf-reset .block-exception { - -moz-border-radius: 16px; - -webkit-border-radius: 16px; border-radius: 16px; margin-bottom: 20px; background-color: #f6f6f6; @@ -86,8 +84,6 @@ .sf-reset .error-count span { display: inline-block; background-color: #aacd4e; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; border-radius: 6px; padding: 4px; color: white; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig index 273749ffd6db3..121a9ee5d6964 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig @@ -2,7 +2,7 @@ {% block head %} {% if collector.hasexception %} - {% endif %} @@ -11,7 +11,7 @@ {% block menu %} - Exception + Exception Exception {% if collector.hasexception %} 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 0c2c45a859f9f..3b72965c3366a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -5,7 +5,7 @@ {% block toolbar %} {% if collector.counterrors or collector.countdeprecations %} {% set icon %} - Logs + Logs {% if collector.counterrors %} {% set status_color = "red" %} {% else %} @@ -34,7 +34,7 @@ {% block menu %} - Logger + Logger Logs {% if collector.counterrors or collector.countdeprecations %} {% set error_count = collector.counterrors + collector.countdeprecations %} @@ -55,7 +55,7 @@ Filter - + @@ -94,8 +94,8 @@ DEPRECATION - {{ log.message }} {% set id = 'sf-call-stack-' ~ log_index %} - - + + + + {% for index, call in log.context.stack if index > 1 %} {% if index == 2 %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig index 2b6dc69978af7..7a1d5f573c65e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig @@ -3,14 +3,14 @@ {% block toolbar %} {% set icon %} - Memory Usage + Memory Usage {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MB {% endset %} {% set text %}
Memory usage - {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MB + {{ '%.1f'|format(collector.memory / 1024 / 1024) }} / {{ collector.memoryLimit == -1 ? '∞' : '%.1f'|format(collector.memoryLimit / 1024 / 1024)|escape }} MB
{% endset %} {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': false } %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index 3bff903a89832..9c0ea3fb5e83c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -15,16 +15,16 @@ {% set request_status_code_color = (400 > collector.statuscode) ? ((200 == collector.statuscode) ? 'green' : 'yellow') : 'red'%} {% set request_route = collector.route ? collector.route : 'NONE' %} {% set icon %} - Request - {{ collector.statuscode }} + Request + {{ collector.statuscode }} {{ request_handler }} on {{ request_route }} {% endset %} {% set text %} {% spaceless %}
- Status Code - {{ collector.statuscode }} + Status + {{ collector.statuscode }} {{ collector.statustext }}
Controller @@ -45,7 +45,7 @@ {% block menu %} - Request + Request Request {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig index 1e1f3287d5899..782465dc61b80 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/router.html.twig @@ -5,7 +5,7 @@ {% block menu %} - Routing + Routing Routing {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index 0d37b97549887..6470ea52501ca 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -18,7 +18,7 @@ {% block toolbar %} {% set duration = collector.events|length ? '%.0f ms'|format(collector.duration) : 'n/a' %} {% set icon %} - Time + Time {{ duration }} {% endset %} {% set text %} @@ -32,7 +32,7 @@ {% block menu %} - Timeline + Timeline Timeline {% endblock %} @@ -50,7 +50,7 @@ {% block panelContent %}
- + @@ -62,7 +62,7 @@ - +
Total time
Threshold ms ms
@@ -91,7 +91,7 @@ {% endfor %} {% endif %} -