tag:blogger.com,1999:blog-49388252395745960162024-03-13T16:18:12.349+01:00Steevan BARBOYONFreelance PHP7 / Symfony3 à LyonSteevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.comBlogger99125tag:blogger.com,1999:blog-4938825239574596016.post-18744564555177843482020-02-25T17:30:00.000+01:002020-02-25T17:30:38.357+01:00Lancer phpcs localement et via CircleCI<div class="bg bg-php">
Pour utiliser <a href="https://github.com/steevanb/php-code-sniffs" target="_blank">steevanb/php-code-sniffs</a> dans vos projets, et pouvoir le lancer localement + via CircleCI, voilà un exemple :
<br><br>
<h3>bin/phpcs</h3>
<div class="code">#!/usr/bin/env sh
set -e
if [ $(which docker || false) ]; then
readonly PROJECT_DIRECTORY=$(realpath $(dirname $(realpath $0))/..)
docker run \
--rm \
-v ${PROJECT_DIRECTORY}:/var/repository:ro \
--entrypoint=/var/repository/bin/phpcs \
steevanb/php-code-sniffs:3.0.1
else
/var/steevanb/php-code-sniffs/vendor/bin/phpcs \
--warning-severity=0 \
--ignore=/var/repository/vendor/ \
--standard=/var/steevanb/php-code-sniffs/vendor/steevanb/php-code-sniffs/Steevanb/ruleset.xml \
--bootstrap=/var/repository/phpcs.bootstrap.php \
--report=steevanb\\PhpCodeSniffs\\Reports\\Steevanb \
/var/repository
fi</div>
</div>
<br>
<h3>.circleci/config.yml</h3>
<div class="code">version: '2.1'
jobs:
phpcs:
docker:
- image: steevanb/php-code-sniffs:3.0.1
working_directory: /var/repository
steps:
- checkout
- run:
name: phpcs
command: bin/phpcs
workflows:
version: '2.1'
CI:
jobs:
- phpcs</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-20333717751096034542019-10-17T15:46:00.000+02:002019-10-17T16:02:46.833+02:00Installer maglnet/composer-require-checker<div class="bg bg-php">
<a href="https://github.com/maglnet/ComposerRequireChecker" target="_blank">maglnet/composer-require-checker</a> va vérifier que vous avez bien toutes les dépendances utilisées indiquées dans votre <i>composer.json</i>.
<br><br>
Pour l'installer dans votre projet :
<div class="code">composer require --dev maglnet/composer-require-checker</div>
Vous pouvez créer un binaire <i>bin/composerRequireChecker</i> qui l'exécute dans un container Docker :
<div class="code">#!/usr/bin/env sh
#set -e
if [ $(which docker || false) ]; then
readonly PROJECT_DIRECTORY=$(realpath $(dirname $(realpath $0))/..)
docker run \
--rm \
-it \
-v ${PROJECT_DIRECTORY}:/var/repository \
-w /var/repository \
--entrypoint=sh \
php:7.3.8-cli-alpine3.10 \
bin/composerRequireChecker $@
else
php -d memory_limit=512M vendor/bin/composer-require-checker $@
fi</div>
Et lancer ce container via CircleCI :
<div class="code">version: '2.1'
jobs:
composer:
docker:
- image: composer
working_directory: /var/repository
steps:
- checkout
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- run:
command: |
if [ ! -f vendor/autoload.php ];then
composer install --ignore-platform-reqs --no-interaction --no-progress --classmap-authoritative;
fi
- save_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths:
- ./vendor
- persist_to_workspace:
root: .
paths:
- vendor
composerRequireChecker:
docker:
- image: php:7.3.8-cli-alpine3.10
working_directory: /var/repository
steps:
- checkout
- restore_cache:
keys:
- composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- run:
name: composerRequireChecker
command: bin/composerRequireChecker
workflows:
version: '2.1'
Code quality:
jobs:
- composer
- composerRequireChecker:
requires:
- composer
</div>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-35132796874444308882019-08-01T14:20:00.001+02:002019-08-01T14:20:25.986+02:00Ajouter BlackFire dans votre container PHP<div class="bg bg-php">
<a href="https://blackfire.io" target="_blank">BlackFire</a> est un outil pour vous aider à comprendre ce qu'il se passe dans votre code, et trouver les goulots d'étranglement pour améliorer les performances.
<br><br>
Voilà un exemple d'installation dans un container PHP :
<br><br>
<strong>Dockerfile</strong>
<div class="code">FROM php:7.3.7-cli
# Install BlackFire
RUN echo "foo"
RUN \
apt-get update \
&& apt-get install -y gnupg2 wget \
&& wget -q -O - https://packages.blackfire.io/gpg.key | apt-key add - \
&& echo "deb http://packages.blackfire.io/debian any main" | tee /etc/apt/sources.list.d/blackfire.list \
&& apt-get update \
&& apt-get install -y blackfire-agent blackfire-php
# Configure BlackFire
COPY .blackfire.ini /root/.blackfire.ini
ARG BLACKFIRE_CLIENT_ID
ARG BLACKFIRE_CLIENT_TOKEN
ARG BLACKFIRE_SERVER_ID
ARG BLACKFIRE_SERVER_TOKEN
RUN \
sed "s/^client-id=$/client-id=${BLACKFIRE_CLIENT_ID}/" -i /root/.blackfire.ini \
&& sed "s/^client-token=$/client-token=${BLACKFIRE_CLIENT_TOKEN}/" -i /root/.blackfire.ini \
&& sed "s/^server-id=$/server-id=${BLACKFIRE_SERVER_ID}/" -i /etc/blackfire/agent \
&& sed "s/^server-token=$/server-token=${BLACKFIRE_SERVER_TOKEN}/" -i /etc/blackfire/agent
</div>
<strong>blackfire.sh</strong>
<div class="code">#!/usr/bin/env bash
readonly BLACKFIRE_CLIENT_ID=
readonly BLACKFIRE_CLIENT_TOKEN=
readonly BLACKFIRE_SERVER_ID=
readonly BLACKFIRE_SERVER_TOKEN=
</div>
<strong>.blackfire.ini</strong>
<div class="code">[blackfire]
ca-cert=
client-id=
client-token=
endpoint=https://blackfire.io
http-proxy=
https-proxy=
timeout=15s
</div>
<strong> Builder votre image</strong>
<div class="code">source blackfire.sh
docker build \
-t ${PROJECT_NAME} \
--build-arg BLACKFIRE_CLIENT_ID=${BLACKFIRE_CLIENT_ID} \
--build-arg BLACKFIRE_CLIENT_TOKEN=${BLACKFIRE_CLIENT_TOKEN} \
--build-arg BLACKFIRE_SERVER_ID=${BLACKFIRE_SERVER_ID} \
--build-arg BLACKFIRE_SERVER_TOKEN=${BLACKFIRE_SERVER_TOKEN}
</div>
<br><br>
Ensuite pour profiler du PHP en CLI :
<br>
<div class="code">blackfire run php ...</div>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-3859061197760257002018-10-17T16:08:00.000+02:002018-10-17T16:09:01.155+02:00Connexion en root impossible à l'installation de MySQLSous Ubuntu 18.04, et avec MySQL 5.7, le mot de passe de root n'est pas demandé lors de l'installation.
<br>
Voilà une méthode qui fonctionne pour le définir :
<div class="code">sudo service mysql stop
sudo mkdir -p /var/run/mysqld
sudo chown mysql:mysql /var/run/mysqld
sudo /usr/sbin/mysqld --skip-grant-tables --skip-networking &
mysql -u root</div>
<div class="code">FLUSH PRIVILEGES;
USE mysql;
UPDATE user SET authentication_string=PASSWORD("root) WHERE User='root';
UPDATE user SET plugin="mysql_native_password" WHERE User='root';
exit;
</div>
Vous pouvez maintenant vous connecter via root / root.
<br><br>
Source : <a href="https://linuxconfig.org/how-to-reset-root-mysql-password-on-ubuntu-18-04-bionic-beaver-linux">linuxconfig.org</a>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-33091468074986391032018-04-23T08:37:00.003+02:002018-05-09T11:58:47.423+02:00Nouveautés dans Symfony 4.1 (mai 2018)<div class="bg bg-sf2">
Voici la liste des nouveautés de Symfony 4.1, sera disponible fin mai 2018 :
<ul>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-serialize-and-deserialize-from-abstract-classes" target="_blank">Serialize and deserialize from abstract classes</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-html5-email-validation" target="_blank">HTML5 Email Validation</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-prefix-imported-route-names" target="_blank">Prefix imported route names</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-invokable-event-listeners" target="_blank">Invokable event listeners</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service" target="_blank">Getting container parameters as a service</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-deprecated-the-advanceduserinterface" target="_blank">Deprecated the AdvancedUserInterface</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-dynamic-lock-refresh" target="_blank">Dynamic lock refresh</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-4-1-twig-extensions-priority" target="_blank">Twig extensions priority</a></li>
<li><a href="https://symfony.com/blog/new-in-symfony-4-1-ignore-specific-http-codes-from-logs" target="_blank">Ignore specific HTTP codes from logs</a></li>
<li><a href="https://symfony.com/blog/new-in-symfony-4-1-ajax-improvements" target="_blank">Ajax improvements</a></li>
<li><a href="https://symfony.com/blog/new-in-symfony-4-1-serializer-improvements" target="_blank">Serializer improvements</a></li>
<li><a href="https://symfony.com/blog/new-in-symfony-4-1-self-updating-debug-toolbar" target="_blank">Self-updating debug toolbar</a></li>
<li><a href="https://symfony.com/blog/new-in-symfony-4-1-advanced-console-output" target="_blank">Advanced Console Output</a></li>
<li><a href="https://symfony.com/blog/new-in-symfony-4-1-console-improvements" target="_blank">Console improvements</a></li>
</ul>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-83932743742549873252018-01-16T18:32:00.000+01:002018-01-16T18:32:39.817+01:00Migration Symfony 3.4 vers 4.0<div class="bg bg-sf2">
Quand j'ai commencé à développer le site <a href="http://www.phpbenchmarks.com" target="_blank">phpbenchmarks.com</a>, la dernière version de Symfony était la 3.3.
<br>
Depuis, la 4.0 est sortie, apportant énormément de changements dans l'architecture du code et du projet.
<br>
<strong>J'ai effectué cette migration en 3h</strong>, pour un site très simple, qui ne contient que 6 pages, sans code très complexe.
<br><br>
<h3 class="post-title">Installation de Symfony 4.0</h3>
<br>
J'ai décidé d'installer un Symfony 4 à côté du projet, pour voir l'architecture, le <span class="highlight">composer.json</span> etc.
<br>
Ensuite, j'ai copié ce qui m'intéressait dans mon projet : les dépendances dans <span class="highlight">composer.json</span>, le répertoire <span class="highlight">/config</span>, le contenu de <span class="highlight">/public/index.php</span> etc.
<br>
Là, premier bon point : Symfony est très léger par défaut !
<br>
Les répertoires sont plus cohérents. Je n'avais jamais compris pourquoi <span class="highlight">/app</span> et <span class="highlight">/src</span> étaient séparés, avec le Kernel dans <span class="highlight">/app</span>.
<br>
On ne savait pas trop si on faisait un seul bundle dans <span class="highlight">/src</span> ou plusieurs, le débat étant sans fin.
<br>
Maintenant, c'est clair : plus de bundle dans <span class="highlight">/src</span>, suppression du répertoire <span class="highlight">/app</span> (tout le code PHP est dans <span class="highlight">/src</span>), le répertoire <span class="highlight">/config</span> est à la racine et contient un fichier par bundle etc.
<br><br>
<h3 class="post-title">Installation des dépendances</h3>
<br>
Au final, on se rend vite compte que le Symfony installé de base ne suffira pas pour notre projet. <strong>C'est bien là le but de Flex</strong> : installer des dépendances très facilement, et surtout, que quand on en a besoin ! Ca va nous éviter d'avoir le composant LDAP installé par exemple, alors qu'on ne s'en sert pas.
<br>
Pour ça, Flex fait très bien son boulot. Un simple <span class="highlight">composer require translator</span> pour installer et configurer <span class="highlight">symfony/translator</span>, sans se poser de questions et lire une doc compliquée. On peut très vite voir les fichiers ajoutés ou modifiés par cette dépendance. Top !
<br><br>
Ce qui l'est moins, à mon sens, c'est qu'une dépendance souvent basique en a d'autres derrière, et qu'on se retrouve très vite à installer énormément de composants de Symfony.
<br>
<strong>Au final, on se retrouve quasiment avec un Symfony full stack</strong>, avec seulement quelques dépendances en moins. J'aurais bien aimé ne pas avoir le <span class="highlight">translator</span> installé par quasiment toutes les dépendances par exemple.
<br><br>
<h3 class="post-title">Suppression des bundles</h3>
<br>
Le débat pas de bundle / AppBundle / un bundle par "section" du site est enfin terminé : <strong>pas de bundle a gagné</strong> !
<br>
On retrouve quasiment la structure interne d'un bundle, directement dans <span class="highlight">/src</span> : <span class="highlight">/Controller</span>, <span class="highlight">/EventListener</span> etc.
<br>
Après avoir déplacé tous ces fichiers, il va falloir renommer tous les <span class="highlight">use</span> PHP (attention au regroupement des use depuis PHP 7.1, une simple recherche ne retrouve pas toujours tout), les appels à <span class="highlight">constant()</span> dans Twig (attention au double <span class="highlight">\\</span> dans la recherche) etc.
<br><br>
<h3 class="post-title">Suppression des bundles (2)</h3>
<br>
Avec la suppression des bundles, que se passe-t-il avec tous les fichiers qui étaient automatiquement inclus et lus par Symfony 3.4 ? <strong>Et bin, rien. On doit tout définir manuellement.</strong>
<br><br>
Par exemple, j'avais une configuration pour changer le paramètre <span class="highlight">router.options.generator_base_class</span> (déprécié en 3.3, supprimé en 4.0 donc), pour utiliser mon générateur d'url.
<br>
J'ai du passer par un CompilerPass, définit manuellement (le répertoire <span class="highlight">/src/DependencyInjection</span> n'est pas lu automatiquement, comme pour un Bundle), dont voici le code :
<div class="code">class UrlGeneratorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$definition = $container->getDefinition('router.default');
$options = $definition->getArgument(2);
$options['generator_class'] = UrlGenerator::class;
$options['generator_base_class'] = UrlGenerator::class;
$definition->setArgument(2, $options);
$container->setDefinition('router.default', $definition);
}
}</div>
Et l'intégration dans Symfony 4, dans <span class="highlight">/src/Kernel.php</span> :
<div class="code">protected function buildContainer(): ContainerInterface
{
$container = parent::buildContainer();
$container->addCompilerPass(new UrlGeneratorPass());
return $container;
}</div>
<br><br>
<h3 class="post-title">Le routing</h3>
<br>
<strong>Premier gros changement</strong> : le suffix <span class="highlight">Action</span> dans le nom des méthodes des Controller n'est plus automatiquement géré. Donc soit vous supprimez tous les suffixes <span class="highlight">Action</span> dans le nom de vos méthodes, soit vous ajoutez <span class="highlight">Action</span> dans toutes les configurations de route <span class="highlight">_controller</span> (qui a un peu changé aussi). Ca peut prendre pas mal de temps, les dépendances externes peuvent ne pas avoir encore effectué la modification, etc.
<br><br>
Dans vos configurations de route, une configuration <span class="highlight">controller</span> a été ajoutée, pour remplacer <span class="highlight">defaults: {_controller: FooController}</span>, qui n'était pas très intuitive.
<div class="code">foo:
path: /foo
methods: GET
controller: App\Controller\FooController::bar</div>
<br>
La syntaxe est devenue très précise : <strong>ce n'est pas la même si le Controller est un service ou non</strong>.
<br>
Si c'est un service : <span class="highlight">controller: service:index</span>, sinon : <span class="highlight">controlller: App\Controller\FooController::index</span>.
<br>
Notez le subtil <span class="highlight">:</span> simple pour indiquer que c'est un service, de nom <span class="highlight">service</span>, et le <span class="highlight">::</span> double, pour indiquer que ce n'est pas un service, et qu'indique le FQCN du Controller.
<br><br>
<h3 class="post-title">Les Controller en service</h3>
<br>
Par défaut, la configuration définit tous ce qui est dans <span class="highlight">/src/Controller</span> comme étant un service (via <a href="https://symfony.com/doc/current/service_container/autowiring.html" target="_blank">l'autowiring</a>), dont l'id est le FQCN du Controller.
<br>
J'ai voulu essayer, parceque ça me parait bien mieux que d'avoir une dépendance vers le <span class="highlight">Container</span> dans les Controller.
<br>
Finalement, comme mes actions ont des dépendances très différentes, je me suis vite retrouvé avec un <span class="highlight">__contruct()</span> qui contient trop d'arguments inutiles pour l'action en cours.
<br>
De plus, si notre Controller implémente <span class="highlight">ContainerAwareInterface</span> ou étend de <span class="highlight">AbstractController</span>, le container sera injecté dans notre Controller (voir <a href="https://github.com/symfony/framework-bundle/blob/v4.0.3/Controller/ControllerResolver.php#L60" target="_blank">ControllerResolver</a>). Logique pour l'interface, mais pas intuitif pour <span class="highlight">AbstractController</span>. Vu que la majorité des Controller étendent d'<span class="highlight">AbstractController</span>, on se retrouve au final avec des dépendances précises dans <span class="highlight">__construct()</span>, mais également le <span class="highlight">Container</span>, ce qui fait doublon et n'est pas forcément le comportement voulu au départ.
<br><br>
<strong>Au final, j'ai supprimé l'autowiring pour les Controller.</strong>
<br><br>
<h3 class="post-title">L'autowiring</h3>
<br>
La configuration par défaut définit tous ce qui est dans <span class="highlight">/src</span> comme étant un service, et va chercher les dépendances automatiquement dans la signature de la méthode <span class="highlight">__construct</span> si elle existe.
<br>
On peut se dire que c'est génial, qu'on ne touchera plus à un <span class="highlight">/Resources/config/service.yml</span> de notre vie !
<br><br>
<strong>La réalité n'est pas si évidente</strong> : seule la signature de <span class="highlight">__construct</span> sera gérée automatiquement, si le typage est bien fait, et que les arguments sont des objets. Oubliez les types scalaires, ils ne peuvent pas être gérés automatiquement.
<br><br>
Donc tous les appels qu'on faisait par <span class="highlight">calls</span> dans <span class="highlight">services.yml</span> ne sont pas gérés.
<br>
La syntaxe <span class="highlight">@=service('doctrine').getRepository('App\Entity\Foo')</span> n'est pas gérée.
<br>Les paramètres ne sont pas gérés (<span class="highlight">%kernel.root_dir%</span> par exemple).
<br><br>
Pour ma part, j'ai vite déchanté, vu que j'utilise régulièrement ces configurations. <strong>Donc j'ai enlevé l'autowiring</strong>, qui ne me servait que très peu, pour conserver ma configuration manuelle dans <span class="highlight">/config/services.yaml</span>.
<br><br>
Autre problème de l'autowiring : <strong>l'identifiant du service devient le FQCN</strong>. On ne peut pas en spécifier un manuellement, indiquer un préfixe, ou un Formatter qui créerait un identifiant de service suivant certaines règles. Du coup, tout le principe d'identifiant pour encapsuler le FQCN, et ne changer qu'un fichier de config en cas de renommage d'une classe n'est plus utilisé : <strong>vous changez un nom de classe, vous changez tous les appels à ce service</strong> !
<br>De plus, je ne suis pas fan des configurations en 4 lignes, qui vont parser tout mon code, et peut-être faire 99% de bonnes choses, mais 1% de bétises qui vont bien nous faire galérer.
<br><br>
<h3 class="post-title">Doctrine</h3>
<br>
Quand on veut passer un Repository en dépendance à un service, on peut utiliser cette syntaxe :
<div class="code">@=service('doctrine').getRepository('FooBundle:BarEntity')</div>
Elle n'est plus intégrée par défaut, il faut installer la dépendance <span class="highlight">symfony/expression-language</span>.
<br>
De plus, avec la suppression des bundles, la syntaxe n'a plus lieu d'être, elle doit être remplacée par :
<div class="code">@=service('doctrine').getRepository('App:BarEntity')</div>
Par défaut, l'installation de Doctrine dans Symfony 4 est configurée pour que les entités soient mappées via des annotations. J'utilise du yml (les <span class="highlight">.orm.yml</span> sont dans <span class="highlight">/config/doctrine</span>), dont voici la configuration dans <span class="highlight">/config/packages/doctrine.yml</span> :
<div class="code">doctrine:
orm:
mappings:
App:
is_bundle: false
type: yml
dir: '%kernel.project_dir%/config/doctrine'
prefix: 'App\Entity'
alias: App
</div>
La configuration par défaut pour le nommage des champs a été changée. Par défaut, le champ SQL pour une propriété <span class="highlight">fooBar</span> d'une entité était <span class="highlight">fooBar</span>. Maintenant, c'est <span class="highlight">foo_bar</span>.
<br>
Pour revenir à l'ancien nommage, il faut supprimer cette configuration :
<div class="code">doctrine:
orm:
naming_strategy: underscore</div>
Plus de <span class="highlight">/app/config/parameters.yml</span> pour la configuration de l'accès à la base de données, tout se passe via une varaible d'environnement : <span class="highlight">DATABASE_URL</span>. Voir la section Virtual Host plus bas pour un exemple.
<br><br>
<h3 class="post-title">Traductions</h3>
<br>
Les fichiers de traductions sont dans <span class="highlight">/translations</span>. Pratique pour les traducteurs, si on veut leur partager un répertoire, sans leur expliquer où aller fouiller dans notre projet !
<br><br>
<h3 class="post-title">Templates</h3>
<br>
Les templates sont dans <span class="highlight">/templates</span>. Pratique pour les intégrateurs, si on veut leur partager un répertoire, sans leur expliquer où aller fouiller dans notre projet !
<br><br>
Attention cependant, la syntaxe pour utiliser un template a changé :
<div class="code">$this->render('FooBundle:Controller:template.html.twig');</div>
devient
<div class="code">$this->render('Controller/template.html.twig');</div>
avec <span class="highlight">template.html.twig</span> dans <span class="highlight">/templates/Controller</span>.
<br><br>
<h3 class="post-title">Virtual host</h3>
<br>
Pour nginx, rien de bien compliqué : il faut simplement changer le répertoire <span class="highlight">/web</span> par <span class="highlight">/public</span>, et appeler <span class="highlight">index.php</span>, quel que soit l'environnement (plus de <span class="highlight">app.php</span>, <span class="highlight">app_dev.php</span> etc).
<br>
Exemple de Virtual Host :
<div class="code">server {
listen 80;
server_name phpbenchmarks.loc;
root /var/www/mysite/public;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index.php(/|$) {
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS off;
fastcgi_param DATABASE_URL 'mysql://USER:PASSWORD@127.0.0.1:3306/DATABASE';
}
error_log /var/log/nginx/mysite_error.log;
access_log /var/log/nginx/mysite_access.log;
}</div>
<br><br>
<h3 class="post-title">PhpStorm</h3>
<br>
Le plugin Symfony pour PhpStorm ne retrouve pas les services définit dans <span class="highlight">/config/services.yaml</span>.
<br>
Un peu déroutant, <strong>on perd toute l'auto-complétion sur nos services</strong>, et les appels à <span class="highlight">$container->get('service')</span> sont tous indiqués en erreur !
<br><br>
Il faut changer les configuration suivantes pour le plugin :
<div class="code">Path to UrlGenerator : var/cache/dev/srcDevDebugProjectContainerUrlGenerator.php
Web directory : public</div>
<br>
J'utilise encore la version 2016 de PHP Storm, c'est possible que la version 2018 corrige quelque détails.
<br><br>
<h3 class="post-title">Et la performance dans tout ça ?</h3>
<br>
La migration finalisée pour <a href="http://www.phpbenchmarks.com" target="_blank">phpbenchmarks.com</a>, j'ai forcément fait un petit benchmark.
<br>
Et là, surprise : <strong>0 différence, pas même 1ms</strong>. Si on se réfère aux benchmarks de <a href="http://www.phpbenchmarks.com/fr/comparator/compare.html?components=symfony-3.4~symfony-4.0&benchmarkType=all&benchmarkTools=apache-bench&phpVersions=php-7.2&concurrencies=" target="_blank">phpbenchmarks</a>, Hello World a un gain énorme entre la 3.4 et la 4.0, mais pas API Rest.
<br><br>
Ca se traduit bien sur mon site : vu que j'ai du installer énormément de dépendances (la plupart encapsulées dans d'autres dépendances), au final, j'ai un framework quasiment complet installé, donc pas de gain grâce à Flex.
</div>
<br><br>
<h3 class="post-title">Conclusion</h3>
<ul>
<li>Le passage de Symfony 3.4 à 4.0 n'est pas aussi simple que de la 2.8 à la 3.0.</li>
<li>Les changements de répertoire sont une très bonne chose, c'est bien plus carré et plus séparé. Cependant, ça peut demander beaucoup de temps, pour renommer tous les namespace, les use, les appels dans Twig, etc.</li>
<li>L'installation de dépendances via Flex est très simple, bien documentée, c'est top !</li>
<li>Je pensais avoir un gain de performance entre la 3.4 et la 4.0, de ce côté je suis un peu déçu.</li>
<li>Je pense que Symfony 4.0 a instauré une nouvelle base, un réel découpage en composants (c'était déjà le cas, mais qui a déjà installé un Symfony composant par composant, manuellement ?), ce qui ne peut qu'être salué !</li>
<li><strong>La migration e m'a pris que 3h</strong>, je pensais en avoir pour bien plus longtemps que ça.</li>
</ul>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-76303072918832030922017-12-01T00:41:00.000+01:002017-12-01T00:41:11.954+01:00PHP 7.2 est là !<div vlass="bg bg-php">
Ca y est, PHP 7.2 est disponible !
<br><br>
Voilà une liste des principales nouveautés :
<ul>
<li><a href="https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts" target="_blank">Convert numeric keys in object/array casts</a> : correction d'un gros bug dans le cast des clefs d'un tableau, quand on convertit un tableau en objet, et un objet en tableau.</li>
<li><a href="https://wiki.php.net/rfc/counting_non_countables" target="_blank">Counting of non-countable objects</a> : ajout d'une E_WARNING quand on appelle count() sur un objet qui n'implément pas \Countable. Continue de retourner 1 dans ce cas (qui ne correspond à rien), pour garder la compatibilité.</li>
<li><a href="https://wiki.php.net/rfc/object-typehint" target="_blank">Object typehint</a> : ajout du type hint object, qu'on peut donc utiliser dans la signature d'une méthode par exemple, pour indiquer qu'une variable est un object dont ne connait pas la classe. Il ne manquait quasiment que ça ! Plus que le typage des propriétés, et peut-être des variables, et on sera enfin débarassé de toutes ces PHPDoc ;).</li>
<li><a href="https://wiki.php.net/rfc/hash-context.as-resource" target="_blank">Migration Hash Context from Resource to Object</a> : enfin une meilleure gestion des resources, qui sont des objets particuliers.</li>
<li><a href="https://wiki.php.net/rfc/argon2_password_hash" target="_blank">Argon2 Password Hash</a> : ajout de Argon2 comme encryptage disponible.</li>
<li><a href="https://wiki.php.net/rfc/improved-tls-constants" target="_blank">Improved SSL / TLS constants</a> : évolution dans la gestion de TLS, qui privilégie la sécurité à la compatibilité entre les versions de PHP.</li>
<li><a href="https://wiki.php.net/rfc/mcrypt-viking-funeral" target="_blank">Deprecate (then Remove) Mcrypt</a> : mcrypt, librairie d'encryptage qui était intégrée au noyau de PHP, n'a plus été mise à jour depuis 2007. Elle est donc supprimée du noyau de PHP 7.2, mais reste disponible via PEAR.</li>
<li><a href="https://wiki.php.net/rfc/libsodium" target="_blank">Make Libsodium a Core Extension</a> : intégration de la librairie de cryptage Sodium dans le noyau de PHP 7.2.</li>
</ul>
<br>
PHP 7.2 devrait être installable facilement sous Linux via le package <a href="https://launchpad.net/~ondrej/+archive/ubuntu/php" target="_blank">ondrej</a>, qui nous fournit depuis quelques années une installation facile de plusieurs versions de PHP en parallèle, toujours à jours.
<br><br>
<center>
<a href="http://php.net/ChangeLog-7.php#7.2.0" target="_blank">Changelog PHP 7.2</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-31722929837597519922017-12-01T00:22:00.001+01:002017-12-01T00:23:24.676+01:00Symfony 4 est là !<div class="bg bg-sf2">
Ca y est, Symfony 4 est disponible !
<br><br>
Parmi les grosses nouveautés :
<ul>
<li><a href="https://medium.com/@fabpot/symfony-4-a-quick-demo-da7d32be323" target="_blank">Symfony Flex</a> : installation plus rapide des dépendances. Pour l'instant je suis pas vraiment convaincu, l'installation n'étant déjà pas compliquée, et ne se fait pas tous les jours. Pas sur que ça change réellement quelque chose, une fois la hype de la 1ère installation passée.</li>
<li><a href="https://symfony.com/doc/current/service_container/3.3-di-changes.html" target="_blank">Auto-registered & autowired services</a> : définition automatique des services, en fonction des paramètres attendus dans le constructeur par exemple. Pratique pour ne plus avoir de fichiers de config complexes, le revers de la médaille c'est qu'on a encore une couche de magie supplémentaire. A voir !</li>
<li>Installation plus light : Symfony 4 est 70% plus léger que Symfony 3.4 à l'installation. Beaucoup de dépendances pas forcément utiles à tous les projets ne sont pas installées par défaut. Ca, c'est top !</li>
<li>Performances : <a href="https://twitter.com/nicolasgrekas/status/929032213815005184" target="_blank">benchmark #1</a>, <a href="https://twitter.com/nicolasgrekas/status/928296996116582406" target="_blank">benchmark #2</a>,
<a href="https://twitter.com/nicolasgrekas/status/911692621684334599" target="_blank">benchmark #3</a>. C'est en bonne voie, mais concrètement, les benchmarks n'indiquent pas de réelles améliorations en prod. A voir quand <a href="http://www.phpbenchmarks.com" target="_blank">phpbenchmarks.com</a> aura fait ses benchmarks pour comparer.</li>
<li><a href="http://fabien.potencier.org/symfony4-directory-structure.html" target="_blank">Directory structure</a> : pas mal de modifications dans l'architecture des répertoires du projet (pas de Symfony ou des bundles).
Ca ressemble plus à du conventionnel, orientation bundle-less pour notre projet (depuis le temps qu'ils veulent se débarasser de ça !), etc. A voir ce que ça va nous apporter, en tout cas l'idée d'être plus conventionnel,
oui !</li>
</ul>
<center>
<a href="http://symfony.com/blog/hello-symfony-4" target=_blank">Annonce de la sortie de Symfony 4</a>
<br>
<a href="http://symfony.com/blog/symfony-4-0-0-released" target="_blank">Changelog Symfony 4</a>
<br>
<a href="http://symfony.com/blog/symfony-3-4-0-released" target="_blank">Symfony 3.4 (Symfony 4 avec les deprecated)</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-69350254502558900382017-10-04T16:46:00.002+02:002017-10-04T16:46:30.260+02:00DoctrineIssue #6751 : EntityRepository et query hints par défaut<div class="bg bg-doctrine">
Si vous définissez des query hints par défaut, pour toutes vos requêtes, sachez que les méthodes de EntityRepository ne les gèrent pas forcément !
<br /><br />
En l'occurence, <i>findAll()</i>, <i>findBy()</i> et <i>findOneBy()</i> n'ajoutent pas ces hints par défaut.
<br /><br />
<center><a href="https://github.com/doctrine/doctrine2/issues/6751" target="_blank">Issue #6751</a></center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-63384891261910908582017-09-20T14:53:00.000+02:002017-09-20T15:08:21.145+02:00Configurations par défaut des bundles<div class="bg bg-sf2">
Quand on configure un bundle, par exemple dans <i>app/config/config.yml</i>, ces 2 configurations devraient avoir le même comportement :
<div class="code">framework:
# pas de clef form</div>
<div class="code">framework:
form: ~</div>
Mais le comportement est différent.
<br /><br />
Pour le premier cas, sans la clef <i>form</i>, le composant Form de Symfony n'est pas chargé, parce que la configuration par défaut de <i>framework.form.enabled</i> est à <i>false</i>.
<br />
Dans le 2ème cas, le composant Form est chargé, alors qu'on s'attend à ce que <i>~</i> soit équivalent à "prend les valeurs par défaut".
<br /><br />
Peut-être que c'est une sorte de raccourci, pour activer le composant Form, sans avoir à définir <i>framework.form.enabled</i> à <i>true</i>.
<br />
Si c'est le cas, la documentation ne le mentionne pas, et il faudrait qu'on ait une liste des configurations qui diffèrent entre ne rien mettre, et mettre <i>~</i>.
<center>
<a href="https://github.com/symfony/symfony/issues/24269" target="_blank">Issue #24269</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-59641181193491708852017-07-13T16:57:00.003+02:002017-11-30T17:45:05.861+01:00Nouveautés dans Symfony 3.4 (novembre 2017)<div class="bg bg-sf2">
Voici la liste des nouveautés de Symfony 3.4, qui est disponible depuis le 30 novembre 2017:
<ul>
<li><a href="http://feedproxy.google.com/~r/symfony/blog/~3/2JJ-H5x0Tpc/new-in-symfony-3-4-validator-information-in-the-symfony-profiler" target="_blank">Validator information in the Symfony profiler</a></li>
<li><a href="http://feedproxy.google.com/~r/symfony/blog/~3/uciSWj07TO4/new-in-symfony-3-4-stopwatch-improvements" target="_blank">Stopwatch improvements</a></li>
<li><a href="http://feedproxy.google.com/~r/symfony/blog/~3/SLWcRNeBlCM/new-in-symfony-3-4-lazy-commands" target="_blank">Lazy commands</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-prefix-all-controller-route-names" target="_blank">Prefix all controller route names</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-added-support-for-xliff-2-0-notes" target="_blank">Added support for XLIFF 2.0 notes</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-user-impersonation-improvements" target="_blank">User impersonation improvements</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-better-console-exceptions" target="_blank">Better console exceptions</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-groups-support-for-the-valid-constraint" target="_blank">Groups support for the Valid constraint</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-http-immutable-responses" target="_blank">HTTP Immutable Responses</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-deprecate-configuration-options" target="_blank">Deprecate configuration options</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-subscribing-to-events-in-the-micro-kernel" target="_blank">Subscribing to events in the micro kernel</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-improved-comparison-constraints" target="_blank">Improved comparison constraints</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-deprecated-bundle-inheritance" target="_blank">Deprecated bundle inheritance</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-better-code-coverage-reports" target=_blank>Better code coverage reports</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-advanced-environment-variables" target="_blank">Advanced environment variables</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-default-request-context-for-assets" target="_blank">Default request context for assets</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-php-based-configuration-for-services-and-routes" target="_blank">PHP-based configuration for services and routes</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-argon2i-password-hasher" target="_blank">Argon2i password hasher</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-bootstrap-4-form-theme" target="_blank">Bootstrap 4 form theme</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-defining-compiler-passes-in-the-kernel" target="_blank">Defining compiler passes in the kernel</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-form-improvements" target="_blank">Form improvements</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-simpler-injection-of-tagged-services" target="_blank">Simpler injection of tagged services</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-improved-the-overriding-of-templates" target="_blank">Improved the overriding of templates</a></li>
<li><a href="Services are private by default" target="_blank">Minimalist PSR-3 logger</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-services-are-private-by-default" target="_blank">Services are private by default</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-local-service-binding" target="_blank">Local service binding</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-debug-form-command" target="_blank">debug:form command</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-session-improvements" target="_blank">Session improvements</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-4-debug-autowiring-command" target="_blank">debug:autowiring command</a></li>
<li><a href="http://symfony.com/blog/symfony-3-4-curated-new-features" target="_blank">Autres petites nouveautés</a></li>
</ul>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-20666845031115877292017-07-05T12:18:00.000+02:002017-07-05T12:18:40.481+02:00Issue #23406 : Doctrine bigint casté en string depuis la 3.2.10<div class="bg bg-sf2">
Une PR, validée pour Symfony 3.2.10 mais pas les autres versions (2.7, 2.8 et 3.3, à voir si la PR n'était pas déjà présente sur ces branches) casse tous les typages PHP pour le type Doctrine <i>bigint</i>.
<br /><br />
Ils ont décidé de caster les bigint Doctrine en string PHP, au lieu de int PHP (comme avant).
<br />
Tous les typages PHP7 sont cassés, et toutes les comparaisons typés avec === également.
<br /><br />
En espérant qu'ils corrigent ça vite, le type bigint est sûrement utilisé par beaucoup de gens pour les identifiants de table !
<br /><br />
<center>
<a href="https://github.com/symfony/symfony/issues/23406" target="_blank">Issue #23406</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-29127028989953724242017-06-27T12:03:00.001+02:002017-06-27T12:03:43.077+02:00Doctrine issue #6509 : PersistentCollection et orphanRemoval<div class="bg bg-doctrine">
Remontée de bug pour Doctrine 2.5.x (et probalement les versions précédentes) : si on appelle <i>PersistentCollection::clear()</i> ou <i>PersistentCollection::removeElement()</i>, et que la liaison a <i>orphanRemoval</i>, alors les éléments supprimés seront enregistrés dans l'UnitOfWork comme étant à supprimer en base de données.
<br /><br />
Jusque là, tout va bien. Mais si on veut de nouveau ajouter un élément dans la PersistentCollection, et que cet élément a été indiqué comme étant à supprimer avant, alors PersistentCollection n'annule pas la demande de suppression.
<br /><br />
Résultat : l'élément supprimé, puis re-ajouté, est supprimé en base, alors qu'on voulait le conserver.
<br /><br />
<center>
<a href="https://github.com/doctrine/doctrine2/issues/6509" target="_blank">Issue #6509</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-84366975764727357482017-06-21T12:06:00.000+02:002017-06-21T12:06:46.229+02:00Symfony Issue #23248 : DataCollectorTranslator::getFallbackLocales() retourne un tableau vide<div class="bg bg-sf2">
En environnement de développement, si on appelle <i>$container->get('translator')->getFallbackLocales()</i>, on a un tableau vide.
<br /><br />
La surcharge DataCollectorTranslator, qu'on n'a qu'en environnement de développement (pour le profiler), gère correctement les fallbacks si le service <i>translator</i> est une instance de <i>Translator</i>.
<br />
Pour les autres implémentations, fournies par Symfony (<i>LogginTranslator</i> par exemple), <i>DataCollectorTranslator</i> retourne un tableau vide.
<br /><br />
<center>
<a href="https://github.com/symfony/symfony/blob/v3.2.9/src/Symfony/Component/Translation/DataCollectorTranslator.php#L100" target="_blank">DataCollectorTranslator.php#100</a>
<br />
<a href="https://github.com/symfony/symfony/issues/23248" target="_blank">Issue #23248</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-75593080620184403342017-06-02T11:15:00.000+02:002017-06-02T11:15:58.492+02:00[symfony/validator] Issue #23032 Valider un tableau valide les valeurs récursivement<div class="bg bg-sf2">
Si on valide un tableau, qui contient un objet <i>parmi ses valeurs</i>, alors cet objet sera validé, avec le groupe de validation utilisé pour le tableau.
<br /><br />
C'est différent pour les objets, qui ont besoin du validateur <i>Valid</i> pour avoir le même comportement.
<br /><br />
<center>
<a href="https://github.com/symfony/symfony/issues/23032" target="_blank">#23032</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-73343323506785824032017-05-23T11:45:00.001+02:002018-09-21T10:53:00.573+02:00Connexion d'un User dans Symfony via un Listener<div class="bg bg-sf2">
On peut avoir besoin de connecter un utilisateur via du code, par exemple pour des tests d'url via <a href="https://github.com/steevanb/php-url-test" target="_blank">steevanb/php-url-test</a>.
<br />
La documentation de Symfony est une base pour comprendre le mécanisme, il manque cependant quelques détails.
<br /><br />
Vous trouverez ci-dessous un exemple complet, qui créé <i>LoginListener</i>, pour connecter votre utilisateur avant que le firewall n'essaye de le récupérer.
<br />
Notez que pour des raisons de sécurité, je met ce code dans <i>app/AppTestKernel.php</i>. Ce fichier sera supprimé de l'environnement de prod.
<br /><br />
Création du listener dans <i>AppTestKernel.php</i> :
<div class="code">protected function getContainerBuilder(): ContainerBuilder
{
$container = parent::getContainerBuilder();
$container->setDefinition(
'foo.login_event_listener',
new Definition(
LoginEventListener::class,
[
new Reference('security.token_storage'),
new Reference('foo.user_provider'),
new Reference('event_dispatcher'),
new Reference('session'),
new Reference('request_stack')
]
)
);
return $container;
}</div>
Ajout du listener sur <i>kernel.request</i>, dans <i>AppTestKernel.php</i> :
<div class="code">public function boot(): void
{
parent::boot();
$this
->getContainer()
->get('event_dispatcher')
// priorité 9 parceque le firewall qui redirige sur la page de login a une priorité de 8
->addListenerService('kernel.request', ['foo.login_event_listener', 'login'], 9);
}</div>
Listener qui log le user <i>admin</i> :
<div class="code"><?php
declare(strict_types=1);
namespace Foo;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\{
RequestStack,
Session\SessionInterface
};
use Symfony\Component\Security\{
Core\Authentication\Token\Storage\TokenStorageInterface,
Core\Authentication\Token\UsernamePasswordToken,
Core\User\UserProviderInterface,
Http\Event\InteractiveLoginEvent
};
class LoginEventListener
{
/** @var TokenStorageInterface */
protected $tokenStorage;
/** @var UserProviderInterface */
protected $userProvider;
/** @var EventDispatcherInterface */
protected $eventDispatcher;
/** @var SessionInterface */
protected $session;
/** @var RequestStack */
protected $requestStack;
public function __construct(
TokenStorageInterface $tokenStorage,
UserProviderInterface $userProvider,
EventDispatcherInterface $eventDispatcher,
SessionInterface $session,
RequestStack $requestStack
) {
$this->tokenStorage = $tokenStorage;
$this->userProvider = $userProvider;
$this->eventDispatcher = $eventDispatcher;
$this->session = $session;
$this->requestStack = $requestStack;
}
public function login(GetResponseEvent $event): void
{
// récupération de l'utilisateur et connexion
$user = $this->userProvider->loadUserByUsername('admin');
$token = new UsernamePasswordToken(
$user,
$user->getPassword(),
'main',
$user->getRoles()
);
$this->tokenStorage->setToken($token);
// l'événement security.interactive_login n'est pas lancé automatiquement
$event = new InteractiveLoginEvent($event->getRequest(), $token);
$this->eventDispatcher->dispatch('security.interactive_login', $event);
// http://symfony.com/doc/current/testing/http_authentication.html
// sauvegarde du token dans la session, pour que le firewall le retrouve
$this->session->set('_security_main', serialize($token));
$this->session->save();
// création du cookie
// si on ne le fait pas, le firewall redirige sur la page de connexion
$this->requestStack->getCurrentRequest()->cookies->set(
$this->session->getName(),
$this->session->getId()
);
}
}
</div>
<center>
<a href="http://symfony.com/doc/current/testing/http_authentication.html" target="_blank">Documentation Symfony</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-28114112103734631492017-05-05T12:36:00.001+02:002017-05-05T12:37:39.110+02:00Feature request : [symfony/console] pouvoir supprimer une commande<div class="bg bg-sf2">
Le composant <i>symfony/console</i> permet d'exécuter des commandes PHP facilement.
<br />
Intégré dans Symfony, il permet à des bundles externes d'ajouter leurs commandes, par exemple <i>doctrine:schema:update</i>.
<br />
Cependant, certaines commandes ne devraient pas être accessibles en fonction de l'environnement, ou du projet.
<br /><br />
Pour reprendre <i>doctrine:schema:update</i>, elle est très utile en <i>dev</i>, mais ne devrait pas pouvoir être lancée en <i>prod</i>.
<br />
Autre exemple : l'application sur laquelle je travaille est découpée en plusieurs projets, dont un qui contient toutes les fixtures, migrations, création de triggers, vues, etc.
<br />Toute la gestion de la base de données est centralisée dans ce projet.
<br />Donc, les commandes <i>doctrine:*</i> n'ont pas lieu d'être dans les autres projets, et surtout, ne profitent pas d'une éventuelle surcharge effectuée par le projet <i>database</i>.
<br /><br />
<center>
<a href="https://github.com/symfony/symfony/issues/22645" target="_blank">Feature request #22645</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-57107387658493602932017-04-26T15:09:00.001+02:002017-04-26T15:09:53.844+02:00Symfony issue #22539 : les commandes ne retournent pas toujours 1 quand une exception est levée<div class="bg bg-sf2">
Les commandes Symfony ne retournent pas forcément 1, si une exception est levée.
<br /><br />
<center>
<a href="https://github.com/symfony/symfony/issues/22539">Issue #22539</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-28103269364113049572017-04-02T19:11:00.005+02:002017-05-31T07:54:56.020+02:00Nouveautés dans Symfony 3.3 (mai 2017)<div class="bg bg-sf2">
Voici la liste des nouveautés de <a href="https://symfony.com/blog/symfony-3-3-0-released" target="_blank">Symfony 3.3</a>, disponible depuis le 30 mai 2017 :
<ul>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-manifest-based-asset-versioning" target="_blank">Manifest-based asset versioning</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-about-command" target="_blank">"about" command</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-better-handling-of-command-exceptions" target="_blank">Better handling of command exceptions</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-lock-component" target="_blank">Lock component</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-load-config-files-with-glob-patterns" target="_blank">Load config files with glob patterns</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-import-config-files-with-glob-patterns" target="_blank">Import config files with glob patterns</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-deprecated-cache-clear-with-warmup" target="_blank">Deprecated cache clear with warmup</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-simpler-service-configuration" target="_blank">Simpler service configuration</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-kernel-build-method" target="_blank">Kernel Build Method</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-deprecated-x-status-code" target="_blank">Deprecated X-Status-Code</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-deprecated-the-classloader-component" target="_blank">Deprecated the ClassLoader component</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-asset-preloading-with-http-2-push" target="_blank">Asset preloading with HTTP/2 Push</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-xliff-linter" target="_blank">XLIFF linter</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-getter-injection" target="_blank">Getter injection</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-deprecated-the-autowiring-types" target="_blank">Deprecated the autowiring types</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-custom-yaml-tags" target="_blank">Custom YAML tags</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-getter-autowiring" target="_blank">Getter autowiring</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-psr-11-containers" target="_blank">PSR-11 containers</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-automatic-console-logging" target="_blank">Automatic Console logging</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-simple-cache" target="_blank">Simple Cache</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-dotenv-component" target="_blank">Dotenv component</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-dependency-injection-deprecations" target="_blank">Dependency Injection deprecations</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-memcached-cache-adapter" target="_blank">Memcached Cache Adapter</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-search-in-dumped-contents" target="_blank">Search in dumped contents</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-optional-class-for-named-services" target="_blank">Optional class for named services</a></li>
<li><a href="http://symfony.com/blog/new-in-symfony-3-3-webserverbundle" target="_blank">WebServerBundle</a></li>
</ul>
<center>
<a href="http://symfony.com/blog/symfony-3-3-0-curated-new-features" target="_blank">Changelog complet</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-30377121984795492882017-03-14T15:16:00.000+01:002017-03-14T15:16:06.033+01:00Git log coloré et plus facile à lire<div class="bg bg-git">Merci à <a href="https://github.com/orgs/Huttopia/people/ZeMarine" target="_blank">Sebastien Huot</a> pour cet alias à <i>git log</i>, avec des couleurs, et plus lisible :
<div class="code">alias gitlog="git log --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --reverse --max-count=50"</div>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-19674198830371147322016-10-31T12:12:00.005+01:002017-02-13T17:23:39.537+01:00Correction de PersistentCollection::clear()<div class="bg bg-doctrine">
Jusqu'à la version 2.5.5, PersistentCollection::clear() ne reset pas les clefs de $this->collection, <i>si la collection est vide</i>.
<br /><br />
Ce qui veut dire que si on a eu des éléments dans $this->collection à un moment, puis qu'ils ont été supprimés "un à un" sans passer par clear(), la prochaine clef ne sera pas 0 mais l'index courant du tableau + 1 (2 si on avait 2 éléments par exemple).
<br /><br />
Ce comportement pourrait être justifié, mais comme les clefs sont reset <i>uniquement si on a des éléments à supprimer</i>, le comportement dans les 2 cas n'est pas le même :
<ul>
<li>si on n'a pas d'éléments à supprimer, les clefs ne sont pas reset</li>
<li>si on a des éléments à supprimer, les clefs sont reset</li>
</ul>
<div class="code">$bar = new Entity();
$bar2 = new Entity();
$foo = new PersistentCollection();
$foo->add($bar);
$foo->add($bar2);
// les clefs seront reset, le prochain appel à add() aura l'index 0
$foo->clear();
$foo->add($bar);
// [0]
var_dump(array_keys($foo->toArray()));
$foo = new PersistentCollection();
$foo->add($bar);
$foo->add($bar2);
$foo->removeElement($bar);
$foo->removeElement($bar2);
// les clefs ne seront pas reset, le prochain appel à add aura l'index suivant
$foo->clear();
// [2]
var_dump(array_keys($foo->toArray()));
</div>
<br />
<center>
<a href="https://github.com/doctrine/doctrine2/blob/v2.5.5/lib/Doctrine/ORM/PersistentCollection.php" target="_blank">PersistentCollection.php#536</a>
<br />
<a href="https://github.com/doctrine/doctrine2/pull/6110" target="_blank">Pull request #6110</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-77057311479008278432016-10-05T18:27:00.011+02:002023-01-10T14:23:16.328+01:00Comment bien coder une manyToOne bidirectionnelle<div class="bg bg-doctrine">
Doctrine2 permet de lier ses entités via 2 types de liaisons : manyToOne et oneToOne.
<br />
La liaison manyToMany n'est qu'un raccourci d'une entité A vers une entité "invisible" via une manyToOne, puis une oneToMany vers votre entité B.
<br />
Toutes les liaisons peuvent être unidirectionnelles (exemple : une manyToOne n'a pas de oneToMany associée) ou bidirectionnelles (exemple : une manyToOne a une oneToMany associée).
<br />
Dans le cas des liaisons bidirectionnelles, il faut définir qui est le propriétaire (owning side), et le côté inverse (inverse side).
<br />
<ul>
<li><b>manyToOne / oneToMany</b> : c'est la manyToOne qui est le owning side, et la oneToMany le inverse side</li>
<li><b>oneToOne</b> : il faut choisir une des deux entités comme owning side, en ajoutant <i>inversedBy</i> côté owning side et <i>mappedBy</i> côté inverse side. Seule la table du owning side aura une clef étrangère vers le inverse side.</li>
<li><b>manyToMany</b> : il faut choisir une des deux entités comme owning side, en ajoutant <i>inversedBy</i> côté owning side et <i>mappedBy</i> côté inverse side. Une table de liaison sera créé par Doctrine2</li>
</ul>
<center>
<a href="http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html" target="_blank">Plus d'informations sur les liaisons Doctrine2</a>
</center>
<br />
<h3 class="post-title">Doctrine2 ne gère que le owning side</h3>
<br />
Doctrine2 ne gère que le owning side des relations : manyToOne, manyToMany owning side, et oneToOne owning side.
<br />
La raison est toute simple : dans votre base de données, le côté owning side est celui qui contient la clef étrangère.
<br />
Prenons pour exemple une entité User, qui est liée à une entité Comment : User > oneToMany > Comment.
<br />
Au niveau de votre base de données, c'est la table comment qui aura un user_id.
<br />
Le fait d'ajouter des Comment sur l'entité User sans appeler Comment::setUser() ne sert à rien. Doctrine2 ne sauvegardera pas votre Comment avec la liaison vers User, et conservera donc null dans Comment::$user.
<br />
Au final, une requête de ce type sera générée :
<div class="code">INSERT INTO comment (user_id, message) VALUES (null, 'mon commentaire')</div>
Grace à cette exemple, on comprend qu'un inverse side (oneToMany, oneToOne inverse side, manyToMany inverse side) n'est finalement qu'un lien pratique pour le développeur, et pas un lien réellement géré par Doctrine2.
<br /><br />
<h3 class="post-title">Coder la oneToMany "parfaite"</h3>
<br />
Beaucoup de bugs peuvent ne pas se voir rapidement quand on code une oneToMany : tentative d'insertion du owning side avec null dans la clef étrangère, suppression des objets côté PHP mais pas en base de données etc.
<br />
Reprenons notre exemple User > oneToMany > Comment :
<div class="code">class User
{
protected Collection $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
/** @param iterable<mixed, Comment> $comments */
public function setComments(iterable $comments): static
{
$this->clearComments();
foreach ($comments as $comment) {
$this->addComment($comment);
}
return $this;
}
public function addComment(Comment $comment): static
{
if ($this->comments->contains($comment) === false) {
$this->comments->add($comment);
$comment->setUser($this);
}
return $this;
}
/** @return Collection<Comment> */
public function getComments(): Collection
{
return $this->comments;
}
public function removeComment(Comment $comment): static
{
if ($this->comments->contains($comment)) {
$this->comments->removeElement($comment);
$comment->setUser();
}
return $this;
}
public function clearComments(): static
{
foreach ($this->getComments() as $comment) {
$this->removeComment($comment);
}
$this->comments->clear();
return $this;
}
}
</div>
<ul>
<li>Utiliser Collection de partout, sauf dans __construct() : compatibilité avec ArrayCollection et PersistentCollection, objet qu'on peut retrouver dans les formulaires via le type entity par exemple</li>
<li>Les PHPDoc avec Collection|Comment[] pour avoir l'auto-complétion à la fois des méthodes de Collection, et de l'objet Comment</li>
<li>setComments() ne doit surtout pas faire $this->comments = new ArrayCollection(), sinon, on ne supprime pas les liaisons du owning side Comment ! Il faut bien appeler clearComments() et addComment()</li>
<li>addComment() doit vérifier que le Comment qu'on veut ajouter n'existe pas déjà, pour éviter tout doublon</li>
<li>addComment() doit appeler Comment::setUser(), pour que le owning side Comment ait connaissance de la valeur à mettre dans la clef étrangère user_id</li>
<li>removeComment() doit appeler Comment::setUser(), pour que le côté owning side Comment sache qu'il n'est plus lié à un User</li>
<li>clearComments() ne doit pas faire $this->comments = new ArrayCollection(), sinon, on ne supprime pas les liaisons du owning side Comment ! Il faut appeler removeComment()</li>
<li>clearComments() doit remettre à 0 le pointeur du tableau interne de Collection, via clear(). Sinon, le prochain appel à add() n'ajoutera pas le commentaire en clef 0, mais en clef 2 (si clearComments() a supprimé les commentaires 0 et 1 par exemple). /!\ Avant Doctrine 2.5.6, l'appel à Collection::reset() pouvait ne pas remettre à 0 les clefs, si le tableau ne contenait pas d'élément.</li>
</ul>
<div class="code">User:
oneToMany:
comments:
targetEntity: Comment
mappedBy: user
orphanRemoval: true
cascade: [persist, remove]
</div>
<ul>
<li>mappedBy est obligatoire côté inverse side, pour indiquer quel est le champ lié du côté ownig side</li>
<li>orphanRemoval est un peu "illogiquement placé" : il permet de dire à Doctrine2 de supprimer tous les Comment qui ont null dans Comment::$user. Cette configuration aurait peut-être due être mise côté owning side Comment, mais c'est comme ça</li>
<li>cascade persist pour sauvegarder les Comment quand on sauvegarde un User</li>
<li>cascade remove pour supprimer tous les Comment quand on supprime un User (à ne pas confondre avec la suppression d'un commentaire en particulier, qui s'effectue via orphanRemoval)</li>
</ul>
<div class="code">class Comment
{
protected ?User $user;
public function __construct()
{
$this->user = null;
}
public function setUser(User $user = null): static
{
$this->user = $user;
if ($user instanceof User) {
$user->addComment($this);
}
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
}</div>
<ul>
<li>Comment::$user peut être du type User, ou null, quand on a "désassocié" un Comment d'un User</li>
<li>setUser() peut donc accepter une instance de User, ou null</li>
<li>getUser() peut donc retourner une instance de User, ou null</li>
</ul>
<div class="code">Comment:
manyToOne:
user:
targetEntity: User
inversedBy: comments
joinColumn:
nullable: false
</div>
<ul>
<li>inversedBy est obligatoire du côté owning side, si on on veut avoir une bidirectionnelle. Sinon, il ne faut pas l'indiquer.</li>
<li>joinColumns.nullable doit être à false si on ne veut pas qu'un commentaire puisse avoir user_id à null</li>
<li>grâce à orphanRemoval du côté inverse side, tous les Comment qui ont null dans Comment::$user seront supprimés</li>
</ul>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com5tag:blogger.com,1999:blog-4938825239574596016.post-12600756262729927812016-09-26T16:33:00.000+02:002016-10-12T11:09:22.471+02:00PHP a 2 types de tableaux<div class="bg bg-php">
Derrière ce titre racoleur se cache une vérité : PHP gère bien 2 types de tableaux. Une version "standard", avec des clefs numériques, et une version "associative", avec des clefs type string.
<br />
<center><a href="http://php.net/manual/fr/language.types.array.php" target="_blank">Documentation PHP sur array</a></center>
<br />
Comment PHP passe d'un tableau standard à un tableau associatif, sans nous le dire, et sans qu'on puisse le voir même avec un var_dump() ?
<br />
Si on ajoute des valeurs dans un tableau qui n'a que des clefs numériques, qui partent de 0, et qui n'ont pas de trou, <b>alors c'est un tableau standard</b>.
<br />
Du moment qu'un tableau a des trous dans ses clefs numériques, ou qu'on ajoute une clef type string, <b>alors c'est un tableau associatif</b>.
<br /><br />
Un auto-cast en int des clefs est effectué en interne. Ce qui veut dire qu'une clef '0' sera automatiquement transformée en intval('0'), et notre tableau final n'aura pas une clef type string mais bien type int.
<br />
Cet auto-cast supprime également les parties décimales des float. Par exemple, une clef 0.5 sera transformée en 0, alors qu'une clef '0.5' restera bien telle quelle.
<br /><br />
Quelques exemples pour illustrer cette explication :
<div class="code">// tableau standard
$array = [ 'foo', 'bar' ];
// type de tableau non modifié, c'est encore un tableau standard
$array[] = 'baz';
// typage transparent en tableau associatif
$array[10] = 'tou';
// tableau standard, même si on met une chaine en clef
// comme c'est '0', c'est casté en int en interne. la clef '0' devient 0.
$array2 = [ '0' => 'foo' ];
// tableau associatif, on n'a pas de clef pour 2
$array3 = [ 0 => 'foo', 1 => 'bar', 3 => 'baz' ];
// tableau standard, l'auto-cast des clefs transforme 1.5 en 1
$array4 = [ 0 => 'foo', 1.5 => 'bar' ];
// tableau associatif, la clef '1.5' conserve son type string
$array5 = [ 0 => 'foo', '1.5' => 'bar' ];
</div>
<br />
<h3 class="post-title">La réaction de json_encode() aux 2 types de tableaux</h3>
<br />
Pour se rendre compte de tout ça, un appel à <i>json_encode($array)</i> peut-être effectué avec les cas de test ci-dessus :
<ul>
<li>Quand le retour est un <b>tableau</b>, c'est un <b>tableau standard</b></li>
<li>Quand le retour est un <b>objet</b>, c'est un <b>tableau associatif</b></li>
</ul>
<b>Cette différence est très importante</b>.
<br />
Par exemple, pour les cas $array3 et $array5, si on fait un bête <i>json_decode(json_encode($array3))</i>, on n'obtient pas un array, mais un \stdClass !
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-6135695215650938292016-09-19T15:13:00.000+02:002016-09-19T15:13:26.683+02:00Issue #6042 : lazy loading inutile si on définit getId() dans un trait<div class="bg bg-doctrine">
Lorsqu'on veut accéder à un identifiant d'une entité qui n'est pas encore chargée par Doctrine, et qu'on passe donc par un proxy, aucun lazy loading n'est effectué parceque le Proxy contient déjà l'identifiant. N'importe quel accès à une autre propriété effectuera un lazy loading.
<br /><br />
Si on définit la méthode getId() d'une entité dans un trait, lors de l'appel à getId(), un lazy loading sera effectué. Le Proxy généré ne comprend pas que la méthode getId() n'accède qu'à l'identifiant, et a le même comportement que n'importe quelle autre accesseur.
<br /><br />
<center><a href="https://github.com/doctrine/doctrine2/issues/6042" target="_blank">Issue #6042</a></center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0tag:blogger.com,1999:blog-4938825239574596016.post-33657935255265662142016-09-05T15:36:00.001+02:002016-09-05T15:38:39.470+02:00Issue #19860 : les constructeurs des Listeners ne sont pas appelés suivant la priorité des services<div class="bg bg-sf2">
Dans le fichier <i>var/cache/classes.php</i>, généré par Symfony, une méthode <i>protected function lazyLoad($eventName)</i> est créée.
<br /><br />
Cette méthode appelle tous les constructeur de tous les listeners d'un même événement, avant même que la méthode liée à l'événement (onKernelRequest par exemple) du listener ayant la priorité la plus haute soit appelée.
<br />
Ces constructeurs ne sont pas appelés selon la priorité des services, mais selon l'ordre d'enregistrement dans le Container.
<br />
De plus, comme tous les constructeurs sont appelés avant les méthodes liées à l'événement, on ne peut pas avoir un listener A qui créé / modifie une donnée dont aura besoin le listener B dans son constructeur.
<br /><br />
<center>
<a href="https://github.com/symfony/symfony/issues/19860" target="_blank">Issue #19860</a>
</center>
</div>Steevan BARBOYONhttp://www.blogger.com/profile/11175619172213648414noreply@blogger.com0