Skip to content

Instantly share code, notes, and snippets.

@mickaelandrieu
Last active May 21, 2024 03:39
Show Gist options
  • Save mickaelandrieu/5211d0047e7a6fbff925 to your computer and use it in GitHub Desktop.
Save mickaelandrieu/5211d0047e7a6fbff925 to your computer and use it in GitHub Desktop.
Complete migration guide from Symfony 2.3 LTS to Symfony 2.7 LTS

From Symfony 2.3 to Symfony 2.7: the complete guide

Objectives

  • assume your code doesn't use any deprecated from versions below Symfony 2.3
  • update dependencies from 2.3 to 2.7
  • do not support "deprecated", be "Symfony3-ready"
  • list tasks component by component, bundle by bundle.

Update your dependencies

Start by updating your composer.json changes to allow for 2.7.*:

{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony" : "2.7.*",
        "...": "..."
    }
}

Now update this with composer:

composer update symfony/symfony sensio/distribution-bundle --with-dependencies

Config

  • The __toString() method of the \Symfony\Component\Config\ConfigCache is marked as deprecated in favor of the new getPath() method.
  • The namespace Symfony\Component\Config\Definition\ReferenceDumper is marked as deprecated, use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper instead.

Console

  • The Symfony\Component\Console\Input\InputDefinition::getSynopsis() method now has an optional argument (it previously had no arguments). If you override this method, you'll need to add this argument so that your signature matches:

    Before:

    public function getSynopsis(){/*..*/}

    After:

    public function getSynopsis($short = false){/*..*/}
  • Console Text and XML representations (and options) are marked as deprecated, and be removed in 3.0.

  • The Symfony\Component\Console\Helper\DialogHelper is deprecated in favor of Symfony\Component\Console\Helper\QuestionHelper.

  • The Symfony\Component\Console\Helper\ProgressHelper is deprecated in favor of Symfony\Component\Console\Helper\ProgressBar.

  • The method getStep() from Symfony\Component\Console\Helper\ProgressBar is deprecated in favor of getProgress().

  • The method setCurrent() from Symfony\Component\Console\Helper\ProgressBar is deprecated in favor of setProgress().

DependencyInjection

  • The [get|set]FactoryClass(), [get|set]FactoryMethod(), [get|set]FactoryService() methods from Symfony\Component \DependencyInjection\Definition class are deprecated, you should use [get|set]Factory() method instead. [Symfony 2.6]

    Before:

    use Symfony\DependencyInjection\Definition;
    
    $definition = new Definition();
    $definition->setFactoryClass('\Foo\Bar')
        ->setFactoryMethod('doBaz');
    
    // or
    $definition->setFactoryService('foo.bar')
        ->setFactoryMethod('doBaz');

    After:

    use Symfony\DependencyInjection\Definition;
    
    $definition = new Definition();
    $definition->setFactory('\Foo\Bar::doBaz');
    
    // or
    $definition->setFactory(array('\Foo\Bar','doBaz'));
    
    // or
    $definition->setFactory(array('foo.bar','doBaz'));
    
    // or
    $definition->setFactory('foo.baz::doBaz');

EventDispatcher

  • The getDispatcher() and getName() methods from Symfony\Component\EventDispatcher\Event are deprecated, the event dispatcher instance and event name can be received in the listener call instead.

    Before:

    use Symfony\Component\EventDispatcher\Event;
    
    class Foo
    {
        public function myFooListener(Event $event)
        {
            $dispatcher = $event->getDispatcher();
            $eventName = $event->getName();
            $dispatcher->dispatch('log', $event);
    
            // ... more code
       }
    }

    After:

    use Symfony\Component\EventDispatcher\Event;
    use Symfony\Component\EventDispatcher\EventDispatcherInterface;
    
    class Foo
    {
        public function myFooListener(Event $event, $eventName, EventDispatcherInterface $dispatcher)
        {
            $dispatcher->dispatch('log', $event);
    
            // ... more code
        }
    }

Form

  • The constructor parameter $precision in IntegerToLocalizedStringTransformer is now ignored completely, because a precision does not make sense for integers.

  • The method FormInterface::getErrors() now returns an instance of Symfony\Component\Form\FormErrorIterator instead of an array. This object is traversable, countable and supports array access. However, you can not pass it to any of PHP's array_* functions anymore. You should use iterator_to_array() in those cases where you did.

    Before:

    $errors = array_map($callback, $form->getErrors());

    After:

    $errors = array_map($callback, iterator_to_array($form->getErrors()));
  • The method FormInterface::getErrors() now has two additional, optional parameters. Make sure to add these parameters to the method signatures of your implementations of that interface.

    Before:

    public function getErrors(){/*..*/}

    After:

    public function getErrors($deep = false, $flatten = true){/*..*/}

    Before:

    {% if form.vars.errors %}

    After:

    {% if form.vars.errors|length %}
  • The "empty_value" option in the types "choice", "date", "datetime" and "time" was deprecated and replaced by a new option "placeholder". You should use the option "placeholder" together with the view variables "placeholder" and "placeholder_in_choices" now.

    The option "empty_value" and the view variables "empty_value" and"empty_value_in_choices" will be removed in Symfony 3.0.

    Before:

    $form->add('category', 'choice', array(
        'choices' => array('politics', 'media'),
        'empty_value' => 'Select a category...',
    ));

    After:

    $form->add('category', 'choice', array(
        'choices' => array('politics', 'media'),
        'placeholder' => 'Select a category...',
    ));

    Before:

    {{ form.vars.empty_value }}
    
    {% if form.vars.empty_value_in_choices %}
        ...
    {% endif %}

    After:

    {{ form.vars.placeholder }}
    
    {% if form.vars.placeholder_in_choices %}
        ...
    {% endif %}
  • In form types and extension overriding the "setDefaultOptions" of the AbstractType or AbstractExtensionType has been deprecated in favor of overriding the new "configureOptions" method.

    The method "setDefaultOptions(OptionsResolverInterface $resolver)" will be renamed in Symfony 3.0 to "configureOptions(OptionsResolver $resolver)".

    Before:

     use Symfony\Component\OptionsResolver\OptionsResolverInterface;
    
     class TaskType extends AbstractType
     {
         // ...
         public function setDefaultOptions(OptionsResolverInterface $resolver)
         {
             $resolver->setDefaults(array(
                 'data_class' => 'AppBundle\Entity\Task',
             ));
         }
     }

    After:

     use Symfony\Component\OptionsResolver\OptionsResolver;
    
     class TaskType extends AbstractType
     {
         // ...
         public function configureOptions(OptionsResolver $resolver)
         {
             $resolver->setDefaults(array(
                 'data_class' => 'AppBundle\Entity\Task',
             ));
         }
     }
  • The "choice_list" option of ChoiceType was deprecated. You should use "choice_loader" and "choices_as_values" now. [Symfony 2.7]

    Before:

    $form->add('status', 'choice', array(
        'choice_list' => new ObjectChoiceList(array(
            Status::getInstance(Status::ENABLED),
            Status::getInstance(Status::DISABLED),
            Status::getInstance(Status::IGNORED),
        )),
    ));

    After:

    $form->add('status', 'choice', array(
        'choices' => array(
            Status::getInstance(Status::ENABLED),
            Status::getInstance(Status::DISABLED),
            Status::getInstance(Status::IGNORED),
        ),
        'choices_as_values' => true,
    ));
  • You should flip the keys and values of the "choices" option in ChoiceType and set the "choices_as_values" option to true. The default value of that option will be switched to true in Symfony 3.0.

    Before:

    $form->add('status', 'choice', array(
        'choices' => array(
            Status::ENABLED => 'Enabled',
            Status::DISABLED => 'Disabled',
            Status::IGNORED => 'Ignored',
        )),
    ));

    After:

    $form->add('status', 'choice', array(
        'choices' => array(
            'Enabled' => Status::ENABLED,
            'Disabled' => Status::DISABLED,
            'Ignored' => Status::IGNORED,
        ),
        'choices_as_values' => true,
    ));
  • Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface was deprecated and will be removed in Symfony 3.0. You should use Symfony\Component\Form\ChoiceList\ChoiceListInterface instead.

    Before:

    use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
    
    public function doSomething(ChoiceListInterface $choiceList){ /*..*/}

    After:

    use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
    
    public function doSomething(ChoiceListInterface $choiceList){/*..*/}
  • Symfony\Component\Form\Extension\Core\ChoiceList\View\ChoiceView was deprecated and will be removed in Symfony 3.0. You should use Symfony\Component\Form\ChoiceList\View\ChoiceView instead.

    Note that the order of the arguments passed to the constructor was inverted.

    Before:

    use Symfony\Component\Form\Extension\Core\ChoiceList\View\ChoiceView;
    
    $view = new ChoiceView($data, 'value', 'Label');

    After:

    use Symfony\Component\Form\ChoiceList\View\ChoiceView;
    
    $view = new ChoiceView('Label', 'value', $data);
  • Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList was deprecated and will be removed in Symfony 3.0. You should use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory instead.

    Before:

    use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
    
    $choiceList = new ChoiceList(
        array(Status::ENABLED, Status::DISABLED, Status::IGNORED),
        array('Enabled', 'Disabled', 'Ignored'),
        // Preferred choices
        array(Status::ENABLED),
    );

    After:

    use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
    
    $factory = new DefaultChoiceListFactory();
    
    $choices = array(Status::ENABLED, Status::DISABLED, Status::IGNORED);
    $labels = array('Enabled', 'Disabled', 'Ignored');
    
    $choiceList = $factory->createListFromChoices($choices);
    
    $choiceListView = $factory->createView(
        $choiceList,
        // Preferred choices
        array(Status::ENABLED),
        // Labels
        function ($choice, $key) use ($labels) {
            return $labels[$key];
        }
    );
  • Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList was deprecated and will be removed in Symfony 3.0. You should use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory::createListFromLoader() together with an implementation of Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface instead.

    Before:

    use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList;
    
    class MyLazyChoiceList extends LazyChoiceList
    {
        public function loadChoiceList()
        {
            // load $choiceList
    
            return $choiceList;
        }
    }
    
    $choiceList = new MyLazyChoiceList();

    After:

    use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
    use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
    
    class MyChoiceLoader implements ChoiceLoaderInterface
    {
        // ...
    }
    
    $factory = new DefaultChoiceListFactory();
    
    $choiceList = $factory->createListFromLoader(new MyChoiceLoader());
  • Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList was deprecated and will be removed in Symfony 3.0. You should use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory instead.

    Before:

    use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
    
    $choiceList = new ObjectChoiceList(
        array(Status::getInstance(Status::ENABLED), Status::getInstance(Status::DISABLED)),
        // Label property
        'name'
    );

    After:

    use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
    
    $factory = new DefaultChoiceListFactory();
    
    $choiceList = $factory->createListFromChoices(array(
        Status::getInstance(Status::ENABLED),
        Status::getInstance(Status::DISABLED),
    ));
    
    $choiceListView = $factory->createView(
        $choiceList,
        // Preferred choices
        array(),
        // Label property
        'name'
    );
  • Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList was deprecated and will be removed in Symfony 3.0. You should use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory instead.

    Before:

    use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
    
    $choiceList = new SimpleChoiceList(array(
        Status::ENABLED => 'Enabled',
        Status::DISABLED => 'Disabled',
    ));

    After:

    use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
    
    $factory = new DefaultChoiceListFactory();
    
    $choices = array(Status::ENABLED, Status::DISABLED);
    $labels = array('Enabled', 'Disabled');
    
    $choiceList = $factory->createListFromChoices($choices);
    
    $choiceListView = $factory->createView(
        $choiceList,
        // Preferred choices
        array(),
        // Label
        function ($choice, $key) use ($labels) {
            return $labels[$key];
        }
    );
  • The "property" option of DoctrineType was deprecated. You should use the new inherited option "choice_label" instead, which has the same effect.

    Before:

    $form->add('tags', 'entity', array(
        'class' => 'Acme\Entity\MyTag',
        'property' => 'name',
    ))

    After:

    $form->add('tags', 'entity', array(
        'class' => 'Acme\Entity\MyTag',
        'choice_label' => 'name',
    ))
  • The "loader" option of DoctrineType was deprecated and will be removed in Symfony 3.0. You should override the getLoader() method instead in a custom type.

    Before:

    $form->add('tags', 'entity', array(
        'class' => 'Acme\Entity\MyTag',
        'loader' => new MyEntityLoader(),
    ))

    After:

    class MyEntityType extends DoctrineType
    {
        // ...
    
        public function getLoader()
        {
            return new MyEntityLoader();
        }
    }
  • Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList was deprecated and will be removed in Symfony 3.0. You should use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader instead.

    Before:

    use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
    
    $choiceList = new EntityChoiceList($em, 'Acme\Entity\MyEntity');

    After:

    use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
    
    $factory = new DefaultChoiceListFactory();
    
    $choices = array(Status::ENABLED, Status::DISABLED);
    $labels = array('Enabled', 'Disabled');
    
    $choiceLoader = new DoctrineChoiceLoader($factory, $em, 'Acme\Entity\MyEntity');
    $choiceList = $factory->createListFromLoader($choiceLoader);
  • Passing a query builder closure to ORMQueryBuilderLoader was deprecated and will not be supported anymore in Symfony 3.0. You should pass resolved query builders only.

    Consequently, the arguments $manager and $class of ORMQueryBuilderLoader have been deprecated as well.

    Note that the "query_builder" option of DoctrineType does support closures, but the closure is now resolved in the type instead of in the loader.

    Before:

    use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
    
    $queryBuilder = function () {
        // return QueryBuilder
    };
    $loader = new ORMQueryBuilderLoader($queryBuilder);

    After:

    use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
    
    // create $queryBuilder
    $loader = new ORMQueryBuilderLoader($queryBuilder);
  • The classes ChoiceToBooleanArrayTransformer, ChoicesToBooleanArrayTransformer, FixRadioInputListener and FixCheckboxInputListener were deprecated and will be removed in Symfony 3.0. Their functionality is covered by the new classes RadioListMapper and CheckboxListMapper.

  • The ability to translate Doctrine type entries by the translator component is now disabled by default and to enable it you must explicitly set the option "choice_translation_domain" to true

    Before:

    $form->add('products', 'entity', array(
        'class' => 'AppBundle/Entity/Product',
    ));

    After:

    $form->add('products', 'entity', array(
        'class' => 'AppBundle/Entity/Product',
        'choice_translation_domain' => true,
    ));
  • In the block choice_widget_options the translation_domain has been replaced with the choice_translation_domain option.

    Before:

    {{ choice.label|trans({}, translation_domain) }}

    After:

    {{ choice_translation_domain is sameas(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}

FrameworkBundle

  • The Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor::renderTable() method expects the table to be an instance of Symfony\Component\Console\Helper\Table instead of Symfony\Component\Console\Helper\TableHelper.

HttpFoundation

  • The PdoSessionHandler to store sessions in a database changed significantly. This introduced a backwards-compatibility break in the schema of the session table. The following changes must be made to your session table:

    • Add a new integer column called sess_lifetime. Assuming you have the default column and table names, in MySQL this would be:

      ALTER TABLE session ADD sess_lifetime INT NOT NULL;

    • Change the data column (default: sess_value) to be a Blob type. In MySQL this would be:

      ALTER TABLE session CHANGE sess_value session_value BLOB NOT NULL;

    There is also an issue that affects Windows servers.

    A legacy class, LegacyPdoSessionHandler has been created to ease backwards-compatibility issues when upgrading.

    The changes to the PdoSessionHandler are:

    • By default, it now implements session locking to prevent loss of data by concurrent access to the same session.
      • It does so using a transaction between opening and closing a session. For this reason, it's not recommended to use the same database connection that you also use for your application logic. Otherwise you have to make sure to access your database after the session is closed and committed. Instead of passing an existing connection to the handler, you can now also pass a DSN string which will be used to lazy-connect when a session is started.
      • Since accessing a session now blocks when the same session is still open, it is best practice to save the session as soon as you don't need to write to it anymore. For example, read-only AJAX request to a session can save the session immediately after opening it to increase concurrency.
      • As alternative to transactional locking you can also use advisory locks which do not require a transaction. Additionally, you can also revert back to no locking in case you have custom logic to deal with race conditions like an optimistic concurrency control approach. The locking strategy can be chosen by passing the corresponding constant as lock_mode option, e.g. new PdoSessionHandler($pdoOrDsn, array('lock_mode' => PdoSessionHandler::LOCK_NONE)). For more information please read the class documentation.
    • The expected schema of the table changed.
      • Session data is binary text that can contain null bytes and thus should also be saved as-is in a binary column like BLOB. For this reason, the handler does not base64_encode the data anymore.
      • A new column to store the lifetime of a session is required. This allows to have different lifetimes per session configured via session.gc_maxlifetime ini setting.
      • You would need to migrate the table manually if you want to keep session information of your users.
      • You could use PdoSessionHandler::createTable to initialize a correctly defined table depending on the used database vendor.

HttpKernel

  • Since version 2.4, the Symfony ProfilerListener constructor method must accept a RequestStack instance to get the request instead of using the Symfony ProfilerListener::onKernelRequest method that will be removed in 3.0.

    Before:

    public function __construct(
        Profiler $profiler,
        RequestMatcherInterface $matcher = null,
        $onlyException = false,
        $onlyMasterRequests = false,
        Request $request = null)

    After:

    public function __construct(
        Profiler $profiler,
        RequestMatcherInterface $matcher = null,
        $onlyException = false,
        $onlyMasterRequests = false,
        RequestStack $requestStack = null)

OptionsResolver

  • The "array" type hint was removed from the OptionsResolverInterface methods setRequired(), setAllowedValues(), addAllowedValues(), setAllowedTypes() and addAllowedTypes(). You must remove the type hint from your implementations.

  • The interface OptionsResolverInterface was deprecated, since OptionsResolver instances are not supposed to be shared between classes. You should type hint against OptionsResolver instead.

    Before:

    protected function configureOptions(OptionsResolverInterface $resolver){/*..*/}

    After:

    protected function configureOptions(OptionsResolver $resolver){/*..*/}
  • OptionsResolver::isRequired() now returns true if a required option has a default value set. The new method isMissing() exhibits the old functionality of isRequired().

    Before:

    $resolver->setRequired(array('port'));
    
    $resolver->isRequired('port'); // => true
    $resolver->setDefaults(array('port' => 25));
    $resolver->isRequired('port'); // => false

    After:

    $resolver->setRequired(array('port'));
    
    $resolver->isRequired('port'); // => true
    $resolver->isMissing('port'); // => true
    $resolver->setDefaults(array('port' => 25));
    $resolver->isRequired('port'); // => true
    $resolver->isMissing('port'); // => false
  • OptionsResolver::replaceDefaults() was deprecated. Use clear() and setDefaults() instead.

    Before:

    $resolver->replaceDefaults(array(
        'port' => 25,
    ));

    After:

    $resolver->clear();
    $resolver->setDefaults(array(
        'port' => 25,
    ));
  • OptionsResolver::setOptional() was deprecated. Use setDefined() instead.

    Before:

    $resolver->setOptional(array('port'));

    After:

    $resolver->setDefined('port');
  • OptionsResolver::isKnown() was deprecated. Use isDefined() instead.

    Before:

    if ($resolver->isKnown('port')) {/*..*/}

    After:

    if ($resolver->isDefined('port')) {/*..*/}
  • The methods setAllowedValues(), addAllowedValues(), setAllowedTypes() and addAllowedTypes() were changed to modify one option at a time instead of batch processing options. The old API exists for backwards compatibility, but will be removed in Symfony 3.0.

    Before:

    $resolver->setAllowedValues(array(
        'method' => array('POST', 'GET'),
    ));

    After:

    $resolver->setAllowedValues('method', array('POST', 'GET'));
  • The class Options was merged into OptionsResolver. If you instantiated this class manually, you should instantiate OptionsResolver now. Options is now a marker interface implemented by OptionsResolver.

    Before:

    $options = new Options();

    After:

    $resolver = new OptionsResolver();
  • Normalizers for defined but unset options are not executed anymore. If you want to have them executed, you should define a default value.

    Before:

    $resolver->setOptional(array('port'));
    $resolver->setNormalizers(array(
        'port' => function ($options, $value) {
            // return normalized value
        }
    ));
    
    $options = $resolver->resolve($options);

    After:

    $resolver->setDefault('port', null);
    $resolver->setNormalizer('port', function ($options, $value) {
        // return normalized value
    });
    
    $options = $resolver->resolve($options);
  • When undefined options are passed, resolve() now throws an UndefinedOptionsException instead of an InvalidOptionsException. InvalidOptionsException is only thrown when option values fail their validation constraints.

    Before:

    $resolver->setDefaults(array(
        'transport' => 'smtp',
        'port' => 25,
    ));
    $resolver->setAllowedTypes(array(
        'port' => 'integer',
    ));
    
    // throws InvalidOptionsException
    $resolver->resolve(array('foo' => 'bar'));
    
    // throws InvalidOptionsException
    $resolver->resolve(array('port' => '25'));

    After:

    $resolver->setDefaults(array(
        'transport' => 'smtp',
        'port' => 25,
    ));
    $resolver->setAllowedTypes(array(
        'port' => 'integer',
    ));
    
    // throws UndefinedOptionsException
    $resolver->resolve(array('foo' => 'bar'));
    
    // throws InvalidOptionsException
    $resolver->resolve(array('port' => '25'));

PropertyAccess

  • The methods isReadable() and isWritable() were added to PropertyAccessorInterface. If you implemented this interface in your own code, you should add these two methods.

  • The methods getValue() and setValue() now throw an NoSuchIndexException instead of a NoSuchPropertyException when an index is accessed on an object that does not implement ArrayAccess. If you catch this exception in your code, you should adapt the catch statement:

    Before:

    $object = new \stdClass();
    
    try {
        $propertyAccessor->getValue($object, '[index]');
        $propertyAccessor->setValue($object, '[index]', 'New value');
    } catch (NoSuchPropertyException $e) {/*..*/}

    After:

    $object = new \stdClass();
    
    try {
        $propertyAccessor->getValue($object, '[index]');
        $propertyAccessor->setValue($object, '[index]', 'New value');
    } catch (NoSuchIndexException $e) {/*..*/}

    A NoSuchPropertyException is still thrown when a non-existing property is accessed on an object or an array.

  • UnexpectedTypeException now expects three constructor arguments: The invalid property value, the PropertyPathInterface object and the current index of the property path.

    Before:

         use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
    
         new UnexpectedTypeException($value, $expectedType);

    After:

         use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
    
         new UnexpectedTypeException($value, $path, $pathIndex);

Routing

  • Added a new optional parameter $requiredSchemes to Symfony\Component\Routing\Generator\UrlGenerator::doGenerate()

  • Route conditions now support container parameters which can be injected into condition using %parameter% notation. Due to the fact that it works by replacing all parameters with their corresponding values before passing condition expression for compilation there can be BC breaks where you could already have used percentage symbols. Single percentage symbol usage is not affected in any way. Conflicts may occur where you might have used % as a modulo operator, here's an example: foo%bar%2 get an error if bar parameter doesn't exist or unexpected result otherwise.

  • The getMatcherDumperInstance() and getGeneratorDumperInstance() methods in the Symfony\Component\Routing\Router have been changed from protected to public. If you override these methods in a subclass, you will need to change your methods to public as well. Note however that this is a temporary change needed for PHP 5.3 compatibility only. It will be reverted in Symfony 3.0.

  • Some route settings have been renamed:

    • The pattern setting for a route has been deprecated in favor of path
    • The _scheme and _method requirements have been moved to the schemes and methods settings

    Before:

    article_edit:
        pattern: /article/{id}
        requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' }
    <route id="article_edit" pattern="/article/{id}">
        <requirement key="_method">POST|PUT</requirement>
        <requirement key="_scheme">https</requirement>
        <requirement key="id">\d+</requirement>
    </route>
    $route = new Route();
    $route->setPattern('/article/{id}');
    $route->setRequirement('_method', 'POST|PUT');
    $route->setRequirement('_scheme', 'https');

    After:

    article_edit:
        path: /article/{id}
        methods: [POST, PUT]
        schemes: https
        requirements: { 'id': '\d+' }
    <route id="article_edit" path="/article/{id}" methods="POST PUT" schemes="https">
        <requirement key="id">\d+</requirement>
    </route>
    $route = new Route();
    $route->setPath('/article/{id}');
    $route->setMethods(array('POST', 'PUT'));
    $route->setSchemes('https');

Security

  • The SecurityContextInterface is marked as deprecated in favor of the Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface and Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface.

    isGranted  => AuthorizationCheckerInterface
    getToken   => TokenStorageInterface
    setToken   => TokenStorageInterface

    The Implementations have moved too, The SecurityContext is marked as deprecated and has been split to use the AuthorizationCheckerInterface and TokenStorage. This change is 100% Backwards Compatible as the SecurityContext delegates the methods.

  • The service security.context is deprecated along with the above change. Recommended to use instead:

    @security.authorization_checker => isGranted()
    @security.token_storage         => getToken()
    @security.token_storage         => setToken()

Serializer

  • The setCamelizedAttributes() method of the Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer and Symfony\Component\Serializer\Normalizer\PropertyNormalizer classes is marked as deprecated in favor of the new NameConverter system.

    Before:

    $normalizer->setCamelizedAttributes(array('foo_bar', 'bar_foo'));

    After:

    use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
    use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
    
    $nameConverter = new CamelCaseToSnakeCaseNameConverter(array('fooBar', 'barFoo'));
    $normalizer = new GetSetMethodNormalizer(null, $nameConverter);
  • Symfony\Component\Serializer\Exception\ExceptionInterface is the new name for the now deprecated Symfony\Component\Serializer\Exception\Exception interface.

Translation

With LoggingTranslator, a new translator class is introduced with Symfony 2.6. By default, the @translator service is referring to this class in the debug environment.

If you have your own services that depend on the @translator service and expect this service to be an instance of either Symfony\Component\Translation\Translator or Symfony\Bundle\FrameworkBundle\Translation\Translator, e.g. by type-hinting for either of these classes, you will need to change that type hint. You can use the TranslatorInterface to be on the safe side for future changes.

Before:

use Symfony\Component\Translation\Translator;

class MyService {
    public function __construct(Translator $translator){/*..*/}
}

After:

use Symfony\Component\Translation\TranslatorInterface;

class MyService {
    public function __construct(TranslatorInterface $translator){/*..*/}
}

TwigBundle

  • The Symfony\Bundle\TwigBundle\TwigDefaultEscapingStrategy is deprecated and no longer used in favor of Twig_FileExtensionEscapingStrategy. This means that CSS files automatically use the CSS escape strategy. This can cause different behaviour when outputting reserved characters.

    Before:

    {# styles.css.twig #}
    
    {# with brand_color: '#123456' #}
    body {
        background: {{ brand_color }};
    }

    After:

    {# styles.css.twig #}
    
    {# with brand_color: '#123456' #}
    body {
        background: {{ brand_color|raw }};
    }

Validator

  • EmailValidator has changed to allow non-strict and strict email validation.

    Before:

    Email validation was done with php's filter_var()

    After:

    Default email validation is now done via a simple regex which may cause invalid emails (not RFC compliant) to be valid. This is the default behaviour.

    Strict email validation has to be explicitly activated in the configuration file:

    framework:
       //...
       validation:
           strict_email: true
       //...
    

    Also you have to add to your composer.json:

    "egulias/email-validator": "~1.2"
  • ClassMetadata::getGroupSequence() now returns GroupSequence instances instead of an array. The sequence implements \Traversable, \ArrayAccess and \Countable, so in most cases you should be fine. If you however use the sequence with PHP's array_*() functions, you should cast it to an array first using iterator_to_array():

    Before:

    $sequence = $metadata->getGroupSequence();
    $result = array_map($callback, $sequence);

    After:

    $sequence = iterator_to_array($metadata->getGroupSequence());
    $result = array_map($callback, $sequence);
  • The array type hint in ClassMetadata::setGroupSequence() was removed. If you overwrite this method, make sure to remove the type hint as well. The method should now accept GroupSequence instances just as well as arrays.

    Before:

    public function setGroupSequence(array $groups){/*..*/}

    After:

    public function setGroupSequence($groupSequence){/*..*/}
  • The validation engine in Symfony\Component\Validator\Validator was replaced by a new one in Symfony\Component\Validator\Validator\RecursiveValidator. With that change, several classes were deprecated that will be removed in Symfony 3.0. Also, the API of the validator was slightly changed. More details about that can be found in UPGRADE-3.0.

    You can choose the desired API via the new "api" entry in app/config/config.yml:

    framework:
        validation:
            enabled: true
            api: auto

    When running PHP 5.3.9 or higher, Symfony will then use an implementation that supports both the old API and the new one:

    framework:
        validation:
            enabled: true
            api: 2.5-bc

    When running PHP lower than 5.3.9, that compatibility layer is not supported. On those versions, the old implementation will be used instead:

    framework:
        validation:
            enabled: true
            api: 2.4

    If you develop a new application that doesn't rely on the old API, you can also set the API to 2.5. In that case, the backwards compatibility layer will not be activated:

    framework:
        validation:
            enabled: true
            api: 2.5

    When using the validator outside of the Symfony full-stack framework, the desired API can be selected using setApiVersion() on the validator builder:

    // Previous implementation
    $validator = Validation::createValidatorBuilder()
        ->setApiVersion(Validation::API_VERSION_2_4)
        ->getValidator();
    
    // New implementation with backwards compatibility support
    $validator = Validation::createValidatorBuilder()
        ->setApiVersion(Validation::API_VERSION_2_5_BC)
        ->getValidator();
    
    // New implementation without backwards compatibility support
    $validator = Validation::createValidatorBuilder()
        ->setApiVersion(Validation::API_VERSION_2_5)
        ->getValidator();
  • The internal method setConstraint() was added to Symfony\Component\Validator\Context\ExecutionContextInterface. With this method, the context is informed about the constraint that is currently being validated.

    If you implement this interface, make sure to add the method to your implementation. The easiest solution is to just implement an empty method:

    public function setConstraint(Constraint $constraint){/*..*/}
  • Prior to 2.6 Symfony\Component\Validator\Constraints\ExpressionValidator would not execute the Expression if it was attached to a property on an object and that property was set to null or an empty string.

    To emulate the old behaviour change your expression to something like this:

    value == null or (YOUR_EXPRESSION)
  • The PHP7-incompatible constraints (Null, True, False) and related validators (NullValidator, TrueValidator, FalseValidator) are marked as deprecated in favor of their Is-prefixed equivalent.

  • The Symfony\Component\Validator\Validator\RecursiveValidator::validateValue() method is deprecated, use the Symfony\Component\Validator\Validator\ValidatorInterface::validate() method instead. [Symfony 2.5]

  • The Symfony\Component\Validator\ConstraintValidator::buildViolation() is deprecated, use the Symfony\Component\Validator\Context\ExecutionContextInterface::buildViolation() instead. [Symfony 2.5]

    Before:

    class ProtocolClassValidator extends ConstraintValidator
    {
        public function validate($protocol, Constraint $constraint)
        {
            if ($protocol->getFoo() != $protocol->getBar()) {
                $this->context->addViolationAt(
                    'foo',
                    $constraint->message,
                    array(),
                    null
                );
            }
        }
    }

    After:

    class ProtocolClassValidator extends ConstraintValidator
    {
        public function validate($protocol, Constraint $constraint)
        {
            if ($protocol->getFoo() != $protocol->getBar()) {
                $this->context->buildViolation($constraint->message)
                    ->atPath('foo')
                    ->addViolation();
            }
        }
    }
  • The Symfony\Component\Validator\ConstraintViolation::getMessageParameters() method is deprecated, use the ConstraintViolation::getParameters() method instead. [Symfony 2.7]

  • The Symfony\Component\Validator\ConstraintViolation::getMessagePluralization() method is deprecated, use the Symfony\Component\Validator\ConstraintViolation::getPlural() instead. [Symfony 2.7]

VarDumper and DebugBundle

The component and the bundle are new to Symfony 2.6. We encourage you to enable the bundle in your app/AppKernel.php for the dev or test environments. Just add this line before loading the WebProfilerBundle:

$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();

Then enjoy dumping variables by calling dump($var) anywhere in your PHP and {% dump var %} or {{ dump(var) }} in Twig. Dumps are displayed in the web debug toolbar.

Yaml

  • The way Yaml handles duplicate keys in an array was changed from rewrite with the last element behavior to ignoring all the elements with the same key after the first one.

    Example:

    parentElement:
        firstChild: foo
        secondChild: 123
        firstChild: bar

    Before:

    This would be parsed in an array like this: ["parentElement" => ["firstChild" => "bar", "secondChild" => 123]]

    After:

    The first value is used: ["parentElement" => ["firstChild" => "foo", "secondChild" => 123]]

Upgrade to Symfony 2.7 without care about deprecated

An elegant way to make your tests pass without upgrade all your code is to use the Symfony PHPUnit brigde created by @nicolas-grekas.

Add to your composer.json file:

{
    "require-dev": {
        "...": "...",
        "symfony/phpunit-bridge" : "~2.7.0",
        "...": "..."
    }
}

Then, if you use travis you need to set SYMFONY_DEPRECATIONS_HELPER to weak:

env:
    global:
        - SYMFONY_DEPRECATIONS_HELPER=weak

The default value is "strict", and result to an error code exit (1) if some deprecations errors remains in your code.

And now all tests broken because a deprecated error will pass. Also a summary of deprecation notices will be displayed at the end of the test suite, take a look to the README.md of the project for more informations.

Sources

@mickaelandrieu
Copy link
Author

By the way, I have just released a new guide to help people migrate Symfony 2 apps to Symfony 3. This is still a work in progress, and I need feedbacks 👍

@vladimir-light
Copy link

vladimir-light commented Jan 20, 2017

thank you so much for this one. Even in 2016/2017 it's still useful. Do you have any other migration guides?

@mickaelandrieu
Copy link
Author

mickaelandrieu commented May 31, 2017

Yes! the link above => migrate Symfony 2 apps to Symfony 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment