Symfony2, c’est plus du tout magique.

Symfony 1.X, c’était magique !

À la base, j’aimais pas trop le PHP. Outre le fait que c’est un langage que je n’ai jamais apprécié,  que se soit à cause de sa lourdeur générale que de sa syntaxe permissive (yes, tu fais n’importes quoi, tu as n’importes quoi !), créer un site web était pour moi une vraie plaie. Il fallait tout le temps vérifier les données, rajouter des filtres pour empêcher les injections de codes, toujours réécrire les mêmes bout de code à chaque fois…

Et puis j’ai découvert… Symfony !

Et là, le plaisir : on crée son schéma de BDD, symfony doctrine:build-all… Et c’est fini.

Symfony créait tout seul les modules CRUD, l’admin, les forms, les filters, la connexion à la base est sécurisée, symfony et doctrine s’occupaient de tout.

Certes, tout n’était pas parfait. Il restait notamment deux lacunes très contraignantes pour moi : l’impossibilité de surcharger un plugin avec un autre plugin, et le fait que Symfony1.X ne gérait pas l’imbrication de formulaires multiples.

  Symfony2 : la chute

Avec une base de travail aussi excellente que Symfony1.X, on aurait pu s’attendre à un framework inégalable. Symfony2 aurait pu être le paradis des développeurs web !

Hélas, Sensio a fait un autre choix : repartir de 0.

Adieu le fameux « schema.yml ». Adieu la génération des modèles, des formulaires, des filtres. Adieu l’admin-générator.

Bref, avec l’intention, certes louable, de vouloir apporter des pratiques de développement encore meilleures que chez son grand frère, on a vu disparaitre tout ce qui faisait la puissance de ce dernier.

Alors oui, c’est plus performant. Oui, le code est plus propre… mais le plaisir n’est plus là.

La première chose qui frappe, c’est la documentation. Alors que Symfony1 présentait une documentation exemplaire, traduite dans les langues majeures, toujours à jour, avec un tutoriel retraçant la création d’une application en conditions réelles de A à Z en 24 jours, Symfony2 ne propose plus qu’une documentation succincte, uniquement en anglais, et pas toujours à jours puisque le framework évolue en permanence.

La seconde chose qui frappe est donc la disparition du fameux fichier schema.yml. La nouvelle politique est qu’il faut créer soit-même les entités (qui correspondent à des tables de la base de donnée, par exemple l’entité « user » correspond à la table « user »). Chaque entité est un fichier php et Symfony les gère via un système d’annotation:

/**
 * @ORM\Column(type="string", length=255)
 *
 * @Assert\NotBlank(message="Please enter your firstname.", groups={"Registration", "Profile"})
 * @Assert\MinLength(limit="3", message="The name is too short.", groups={"Registration", "Profile"})
 * @Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
 */
protected $firstname;

Je suis peut-être vieux-jeu, mais pour moi, les commentaires sont fait pour « commenter », ils sont destinés à la documentation. D’ailleurs vous remarquerez très vite que votre IDE est inutile puisqu’il ne reconnait pas les annotations…

Bref, tout est sur ce modèle du « do it yourself ». Tu veux le formulaire de ton entité ? fais le toi même. Tu veux le contrôleur, idem.

Les commandes proposées par Symfony2 se contentent du minimum syndicale.

Toutefois, tout n’est pas noir, ça reste un bon framework sur le papier, notamment grâce aux nombreux bundles disponibles, comme le fameux FOSUserBundle qui propose un système très complet et complètement paramétrable pour gérer les utilisateurs. De plus, il est maintenant très facile de surcharger un bundle avec un autre bundle. Enfin il semble que les formulaires imbriqués soit théoriquement bien gérés (quoi que chez moi, sur la version 2.1 en pre-release, c’est loin d’être le cas :p).

Conclusion

Symfony2 a deux problèmes majeurs à mes yeux : premièrement il perd son côté « magique ». Et du coup tout son intérêt. Certes, on gagne en performance, on gagne en propreté, mais on continue d’utiliser PHP. Hors, PHP reste (et restera probablement encore longtemps) un langage médiocre. En perdant sa magie, Symfony a du coup perdu tout son intérêt en face des frameworks réellement performants comme Play! (Java/Scala), Django (Python) ou Ruby On Rail (dois-je préciser ?) qui, avec une logique similaire, resteront toujours incomparablement plus performants.

Deuxièmement, Symfony2 a été relâché beaucoup trop tôt. Encore aujourd’hui, les formulaires, qui sont sûrement le composant majeur de Symfony sont en constante évolution. Certes, j’utilise la pre-release, mais cela me permet de constater l’évolution constante de ce framework qui est loin d’être abouti.

Script JQuery pour gérer les embbed forms sous Symfony2

Bonjour,

Dans cet article je vais simplement partager avec vous mon petit script JS pour pouvoir rapidement utiliser les embbed forms de Symfony2. En effet, Symfony2 n’est plus du tout magique et choisi de nous laisser nous dépatouiller avec les embbed forms côté client (et côté serveur, mais c’est un autre sujet que j’aborderais peut-être dans un prochain article).

Pour l’utiliser :
gestion d’ajout/suppression :

  • définissez la classe with_default pour créer un objet vide si aucun n’existe.
  • définissez la classe with_empty pour créer un objet vide quelque soit le cas
  • définissez data-number-to-add pour définir le nombre d’objet vide à ajouter lors du clique sur le bouton d’ajout.
'class' => 'with_default',
'data-number-to-add' => 1

option uniqify (utile par exemple pour cocher une propriété « image principale » sur une gallerie) : définissez la classe uniqify ainsi qu’un data-uniqify-id pour n’autoriser qu’un seul élément à être coché à travers plusieurs formulaire embarqués :

'class' => 'uniqify',
'data-uniqify-id' => 'galleryFormIsMain',

Bref, ne vous laissons pas attendre plus longtemps, voici le script :

// Init vars
addingIndexes = new Array();

$(document).ready(function() {
	
	// Handle uniqifying
	$('input.uniqify').on('click', function(event) {
		console.log('uniqify : ' + $(this).data('uniqify-id'));
		$("input[data-uniqify-id='" + $(this).data('uniqify-id') + "']").prop('checked', false);
		$(this).prop('checked', true);
	});

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Embed forms fonctions
	
	// Initialisation
	/**
	 * This will always add empty object if the embedded form type has class "with_empty"
	 * This will add empty object only if no object exists if the embedded form type has class "with_default"
	 * Number of added object is defined in data-number-to-add attribute of the embedded form type
	 */
	$('div').find('*[data-prototype]').each(function(_id) {
		// init vars
		existingObjects = false;
		// adding remove button for existing embedded forms items
		$(this).children().each(function(_id) {
			$(this).append(createRemoveButton());
			existingObjects = true;
		});
		// creating default empty object for each embedded forms
		if ($(this).hasClass('with_empty') || $(this).hasClass('with_default') && !existingObjects) {
			addObject($(this).attr('id'));
		}
		// creating add button
		$(this).parent().append(createAddButton($(this).attr('id')));
	});


	/**
	 * Function creating embedded forms items
	 * @param {type} _id
	 * @returns {undefined}
	 */
	function addObject(_id) {
		console.log("Embedded form : add object " + _id);
		// handle items indexes in an array
		if(!addingIndexes[_id]) {
			console.log('creating indexes for ' + _id);
			addingIndexes[_id] = $('#' + _id).children().length;
		} else {
			console.log('increment indexes for ' + _id);
			addingIndexes[_id]++;
		}

		// add objects
		numberToAdd = $('#' + _id).data('number-to-add');
		for (i = 0; i < numberToAdd; i++) {
			$('#' + _id).append(
					$($('#' + _id).attr('data-prototype').replace(/__name__label__/g, '').replace(/__name__/g, addingIndexes[_id])).append(createRemoveButton)
			);
		}
	}

	/**
	 * Function creating add button. You can create your own btnAdd js var in your template.
	 * @param {type} _toAddId
	 * @returns {String}
	 */
	function createAddButton(_toAddId) {
		thisBtnAdd = '<a class="add_object" data-objectid="' + _toAddId + '">Add</a>';
		if (typeof btnAdd !== 'undefined') {
			btnAdd = $.parseHTML(btnAdd);
			$(btnAdd).attr('data-objectid', _toAddId);
			thisBtnAdd = btnAdd;
		}
		return thisBtnAdd;
	}

	/**
	 * function creating remove button. You can create your own btnRemove js var in your template.
	 * @returns {String}
	 */
	function createRemoveButton() {
		thisBtnRemove = '<a class="remove_object">Remove</a>';
		if (typeof btnRemove !== 'undefined') {
			thisBtnRemove = btnRemove;
		}
		return thisBtnRemove;
	}

	// Handle add buttons' click event
	$('.add_object').click(function() {
		addObject($(this).attr('data-objectid'));
	});

	// Handle remove buttons' click event
	$('form').on('click', '.remove_object', function() {
		$(this).parent().remove();
	});
	///////////////////////////////////////////////////////////////////////////////////////////////

});

Et voilà, plus qu’à importer votre script dans votre template Twig ! 😉

Si je trouve le courage de faire un système pour laisser la possibilité de personnaliser facilement (et d’internationaliser) les boutons, je mettrais à jour cet article.

Enjoy !

Mise à jour du 10 novembre 2013 :
Le comptage du nombre d’enfant à été corrigé en cas de suppression et de rajout.
L’ajout de configuration par un jeu de classes et de data a été ajouté !
Mise à jour du 10 novembre 2013 :
L’utilisation des fonctions .live() a été remplacé par .on(), merci Thibault pour cette judicieuse remarque 😉
Mise à jour du 24 août 2014 :
l’utilisation de .on() à été mis à jour pour fonctionner avec les dernière version de jQuery et je gère à présent la création des boutons add et remove dans le template twig (donc avec internationalisation)