jeudi 9 juin 2016

Passer de Symfony 2.8 à Symfony 3.1

Etant sur un projet qui a débuté en Symfony 2.6, j'ai du effectuer les migrations vers la 2.7, puis la 2.8.
Concrètement, à part un problème en 2.8.5 sur le NumberType qui retournait un int ou un float, et qui s'est mit à retourner uniquement des float (même si on a saisi 1 par exemple, voir le commit), tout s'est bien passé.

Par contre, et même en faisant très attention à ne pas trop avoir de deprecated dans notre code, et sachant que Symfony 2.8 lui-même générait des centaines de deprecated, le passage à la 3.1 ne s'est pas fait sans mal ... Déjà à cause des bundles utilisés. Il a fallu attendre quelques mois pour que les dépendances fonctionnent (FOSUserBundle par exemple), et certaines ne seront jamais mis à jour (knplabs/doctrine-behaviors).
Voici une liste de ce qui m'a posé soucis, et comment je l'ai corrigé :
  • $validator->validate($value, $constraints = null, $groups = null) : l'ordre des paramètre a changé. Avant, on pouvait passer $groups en 2ème, ou en 3ème, au choix. Depuis la 3.0, $groups est forcément le 3ème paramètre.
  • Knp\DoctrineBehaviors\ORM\Blameable\UserCallable utilisait security.context, qui n'existe plus, au profit de security.token_storage. Comme KNP s'occupe très peu de la migration vers Symfony 3, j'ai du changer la configuration knp.doctrine_behaviors.blameable_subscriber.user_callable.class, pour la faire pointer sur une classe de mon projet, qui utilise le nouveau service.
  • ContainerInterface::isScopeActive() n'existe plus, il a donc fallu faire sans
  • Dans les fichiers YML, il restait quelques chaines sans quotes, qui contenaient des ":". Depuis Symfony 3, le composant YML lève une exception de parsing dans ce cas-là.
  • Quelques configurations de FormType qui n'existent plus, comme pattern, precision, property, etc. Voir la liste ici.
  • Comme l'option choices_as_values n'existe plus, il a fallu repasser sur tous les CollectionType : pour les valeurs du sous-formulaire, avant on passait en clef la valeur à stocker, et en valeur, le libellé à afficher. Maintenant, c'est l'inverse.
  • {% render() %} n'aurait pas du être appelé comme ça, mais plutôt {{ render() }}. C'est obligatoire maintenant.
  • La configuration twig.form.resources devient twig.form_themes
  • La configuration security.firewalls.foo.form_login.csrf_provider: form.csrf_provider devient security.firewalls.foo.form_login.csrf_token_generator: security.csrf.token_manager

Benchmarks entre Symfony 2.8.4 et Symfony 3.1.0


Il faut bien prendre en compte que ces benchmarks sont à tire informatif. Ils ont été fait sur mon PC, donc avec une interface graphique très gourmande, avec Apache Bench, PHP 5.6, sur la page de connexion de mon projet.
Pour chacune des concurrences d'accès, 1 000 appels sont effectués. Le temps affiché est le temps moyen d'une requête. L'environnement est spécifié à côté de la version de Symfony.

Concurrence 1 Concurrence 3 Concurrence 5 Concurrence 10
Symfony 2.8.4
(prod)
81.329 ms
(12.30 req/s)
97.185 ms
(30.87 req/s)
114.103 ms
(43.82 req/s)
218.755 ms
(45.71 req/s)
Symfony 3.1.0
(prod)
113.321 ms
(8.82 req/s)
138.314 ms
(21.69 req/s)
152.142 ms
(32.86 req/s)
299.432 ms
(33.40 req/s)
Symfony 2.8.4
(dev)
230.995 ms 287.655 ms 342.507 ms 672.107 ms
Symfony 3.1.0
(dev)
243.499 ms 301.299 ms 358.609 ms 692.711 ms

On constate donc que l'environnement de prod est environ 3x plus rapide que l'environnement de dev.
Symfony 3.1 est 28% plus lent que Symfony 2.8.4 !

mardi 24 mai 2016

Mettre à jour son serveur MySQL en 5.7

Pour pouvoir utiliser les champs de type json en MySQL, il vous faut la version 5.7. Cependant, cette version étant assez récente, elle n'est pas fournie dans les dépôts d'origine sur toutes les distributions de Linux.

Voilà les commandes à executer pour mettre à jour votre serveur MySQL en 5.7.12, sans perdre les données (testées depuis une Ubuntu 14.04.4 LTS, qui a MySQL 5.6 dans les dépôts) :
wget http://dev.mysql.com/get/mysql-apt-config_0.7.2-1_all.deb sudo dpkg --install mysql-apt-config_0.7.2-1_all.deb
Choisissez MySQL Server au premier choix, puis la version 5.7 ensuite.
De retour au menu précédent, allez sur Ok (dernier choix) et validez.
sudo apt-get update && sudo apt-get upgrade mysql-server sudo mysql_upgrade -uroot -p --force sudo service mysql-server restart

Erreur 3065 suite à la migration

Si suite à cette mise à jour, vous avez ce type de d'erreur :
General error: 3065 Expression #1 of ORDER BY clause is not in SELECT list, references column
C'est un mode de requête qui est activé par défaut depuis la 5.7.5 (migration testée en 5.7.12) : ONLY_FULL_GROUP_BY.
Pour le supprimer, copiez ce que la requête suivante retourne :
SELECT @@GLOBAL.sql_mode;
Ensuite, ouvrez le fichier /etc/mysql/my.cnf, et sous la clef mysqld, ajoutez sql_mode = '' (en valeur : ce que vous avez copié, moins ONLY_FULL_GROUP_BY).

jeudi 21 avril 2016

Corriger les requêtes jouées en boucle par doctrine:schema:update

La commande doctrine:schema:update de Symfony permet de mettre à jour votre base de données, par rapport à votre mapping.
Dans certains cas, cette commande veut exécuter des requêtes qui n'ont pas lieu d'être.
De même si vous utilisez DoctrineMigrations, qui peut vous générer des migrations avec toujours les mêmes requêtes.

Par exemple, si vous voulez créér un type de champ FooDecimal, qui doit définir la valeur par défaut de scale à 2 au lieu de 0 :
Foo\Entity\Bar: fields: baz: type: foodecimal

Vous allez créer un type de champ Doctrine via cette documentation, avec entre autres ce code pour définir scale à 2 par défaut "dans la requête SQL" (pas au niveau mapping YML, XML ou PHP) :
class FooDecimalType extends DecimalType { public function getName() { return 'foodecimal'; } public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) { $fieldDeclaration['scale'] = (empty($fieldDeclaration['scale'])) ? 2 : $fieldDeclaration['scale']; return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration); } /** * Ajoute un commentaire SQL sur le champ (exemple MySQL : COMMENT '(DC2Type:foodecimal)'), avec le type retourné par getName() * Permet de lier le type de champ SQL decimal, au type de champ FooDecimalType * Sans ça, Doctrine se base sur la requête ALTER de création du champ pour "retrouver" le type de champ Doctrine * Le premier trouvé sera DecimalType, fourni avec Doctrine, et pas notre FooDecimalType * Comme le type de champ a changé pour Doctrine, une requête ALTER sera jouée à l'infini, * même si au final elle n'effectue pas de modification en base */ public function requiresSQLCommentHint(AbstractPlatform $platform) { return true; }
Au niveau de votre base de données, la valeur par défaut de scale sera bien 2.

Mais il est impossible de modifier les valeurs par défaut du côté du mapping, cf SchemaTool::gatherColumn(), et YamlDriver::columnToArray() pour le mapping YML par exemple.
Quand vous allez exécuter la commande doctrine:schema:update, SchemaTool::getSchemaFromMetadata() retournera 0 comme valeur pour scale pour votre champ baz du côté du mapping puisqu'il n'y a aucune information dans le fichier YML sur scale, alors que AbstractSchemaManager::createSchema() retournera 2, puisque votre champ en base de données est configuré pour avoir scale à 2.
A ce moment-là, pour Doctrine, le champ en base de données n'est pas le même que ce que le mapping lui indique. Donc, il génère une requête ALTER TABLE, qui modifiera le scale en base de données, pour lui mettre 2 (ce que FooDecimal définit dans la génération du SQL), bien que ce soir déjà le cas. La différence entre le mapping et la base de données est faite dans Doctrine\DBAL\Schema\Comparator::diffColumn().

Pour corriger ce problème de valeurs par défaut non modifiable du côté du mapping, on peut utiliser l'événement postGenerateSchemaTable, qui n'est pas documenté.
services: foo_decimal_default_scale: class: Foo\BarBundle\EventListener\FooDecimalDefaultScaleListener tags: - { name: doctrine.event_listener, event: postGenerateSchemaTable }
<?php namespace Foo\BarBundle\EventListener; use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs; use Foo\BarBundle\Doctrine\Type\FooDecimal; class FooDecimalDefaultScaleListener { public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $event) { foreach ($event->getSchema()->getTables() as $table) { foreach ($table->getColumns() as $column) { if ($column->getType() instanceof FooDecimal && $column->getScale() === 0) { $column->setScale(2); } } } } }

mardi 12 avril 2016

Nouveautés dans Symfony 3.0 (novembre 2015)

Symfony 3.0 n'a aucune nouvelle fonctionnalité, comparé à la version 2.8.

Tout l'intérêt de cette version est de supprimer toutes les E_USER_DEPRECATED levées par Symfony au fil des versions.
Je n'ai pas retrouvé la source, mais de mémoire, sur le site de Symfony, ils disaient avoir supprimé 15% de lignes de code (quelques milliers quand même), pour arriver à ce résultat.
Comment passer de la 2.x à la 3.0

Pour vous aider à trouver les E_USER_DEPRECATED dans votre code, les corriger, et pouvoir passer à Symfony 3.0 :
symfony/phpunit-bridge
deprecation-detector
umpirsky/Symfony-Upgrade-Fixer

mardi 5 avril 2016

Créer un identifiant d'entité Doctrine

Doctrine 2.5 (et sûrement les versions antérieures) permet de mapper un champ d'une entité, en le définissant comme étant son identifiant.

La documentation parait complète, mais il y a quelques erreurs, et des informations de mapping qui ne sont pas reportées sur cette page.
Voici toutes les options possibles pour le mapping d'un identifiant, au format YML :
Foo\Entity\Bar: id: id: type: integer generator: strategy: NONE options: unsigned: false column: id associationKey: my_field length: 50 columnDefinition: INT AUTO_INCREMENT UNSIGNED sequenceGenerator: sequenceName: message_seq allocationSize: 100 initialValue: 1 customIdGenerator: Foo\CustomIfGenerator tableGenerator: Foo\TableGenerator
  • type : type du champ. Je n'ai pas testé tous les types de champs, certains peuvent ne pas fonctionner comme datetime. Liste des types de champs.
  • generator [défaut : NONE] : même si la documentation dit que la valeur par défaut est AUTO, c'est bien NONE la vraie valeur par défaut (cf ClassMetadataInfo, valeur par défaut de $generatorType). Donc par défaut, aucune gestion automatique de l'identifiant n'est effectuée, c'est à vous de faire setId() "avant le persist()". La valeur AUTO est la bonne pour la majorité des cas.
  • options : tableau d'options, chaque type de champ peut avoir ses options. Par exemple pour les types numériques, on peut spécifier unsigned.
  • column [défaut : id] : nom de la colonne dans la base de données.
  • associationKey : Voir la documentation.
  • length [défaut : 255] : utilisé pour les types string et binary, pour indiquer la longueur maximale de la valeur stockée en base.
  • columnDefinition : pour surcharger le code SQL généré dans le CREATE TABLE et ALTER TABLE.
  • sequenceGenerator : configuration de la séquence, utilisée uniquement pour Oracle et Postgres.
  • customIdGenerator : si aucune stratégie de génération d'identifiant ne vous convient, vous pouvez créer une classe qui doit étendre de Doctrine\ORM\Id\AbstractIdGenerator, et indiquer son fully qualified class name ici.
  • tableGenerator : petite blague de Doctrine, même si c'est écrit dans la documentation : la configuration existe, mais elle n'est pas gérée, et lève une MappingException.
Toutes ces informations proviennent de YamlDriver, de la version 2.5 de Doctrine, utilisée dans Symfony 2.8.

Pour résumer, voici la bonne configuration d'un mapping d'identifiant, pour la plupart des cas :

Foo\Entity\Bar: id: id: type: integer # de -2 147 483 648 à 2 147 483 647 en MySQL generator: strategy: AUTO # IDENTITY pour MySQL, SQLite, MsSQL et SQL Anywhere, SEQUENCE pour Oracle et PostgreSQL options: unsigned: true # pas d'identifiants négatifs, change le maximum à 4 294 967 295

jeudi 26 novembre 2015

Symfony 2.7.7 disponible

Symfony 2.7.7 est disponible, avec 23 bugs corrigés, dont 2 failles de sécurité :
  • CVE-2015-8124 : connexion avec un utilisateur, en utilisant son identifiant de session, stocké sans le cookie "Remember me"
  • CVE-2015-8125 : remote timing attacke, en utilisant également le cookie du "Remember me"
Les 2 failles ci-dessus ont également été corrigées dans Symfony 2.3.35 et 2.6.12, mais pas dans les versions 2.4 et 2.5 (qui ne sont plus maintenues).

Changelog Symfony 2.7.7

steevanb/gitscripts 2.4.0 disponible

steevanb/gitscripts 2.4.0 est disponible, avec l'ajout du paramètre --force pour le script deluntrackedbranch.sh.

Avant la 2.4.0, par défaut, deluntrackedbranch.sh effectuait réellement la suppression des branches non traquées par origin.
Maintenant, pour effectuer la suppression, il faut ajouter le paramètre --force.
Sans ce paramètre, la liste des branches non traquées sera affichées, sans suppression effectuée.

Changelog 2.4.0

lundi 21 septembre 2015

steevanb/gitscripts 2.3.0 disponible

steevanb/gitscripts 2.3.0 est disponible, avec l'ajout du script deluntrackedbranch.sh.

Ce script liste les branches du remote origin, les compare avec les branches locales, et supprime les branches locales qui n'existent pas sur origin.
C'est assez utile quand on a avancé dans un projet, et qu'on a beaucoup de branches en locale qui ont été mergées via un outil quelconque (GitLab, GitHub, etc) et qui n'existent plus.

Changelog steevanb/gitscripts

steevanb/sf2-form-utils 1.3.1 disponible

steevanb/sf2-form-utils 1.3.1 est disponible.
Pour rappel, cette librairie permet à la méthode FormType::buildForm() d'être orientée objet, donc de profiter de l'auto-complétion et de rapidement savoir les configurations disponibles pour chaque type de champ.

  • Les setters avec un paramètre de type bool ont une valeur par défaut à true (par exemple, setCascadeValidation(true) peut être directement appelé setCascadeValidation()).
  • La méthode getFieldEntity()->setRepositoryMethod() permet de facilement appeler une méthode de repository, sans avoir à écrire la closure.
  • Les méthodes setAutofocus() et getAutofocus() ont été ajoutées aux types de champs correspondants, ainsi que setPlaceholder() et getPlaceholder().

Changelog steevanb/sf2-form-utils 1.3.1

Symfony 2.7.4 disponible

Symfony 2.7.4 est disponible, avec 40 bugs corrigés, notamment pour la compatibilité PHP 7.

Un gros travail a été effectué pour la compatibilité avec Twig 2.0, ce qui rend Symfony 2.7.4 entièrement compatible avec Twig 1.x et 2.x.

Si vous êtes sur la branche 2.7 de Symfony, vous êtes encouragés à passer à cette version.

Changelog Symfony 2.7.4