Symfony: Multiple embedded forms in a specific layout

First of all I want to state that I’m quite new to the Symfony Framework and that my English does not compare with a native, but hey, I’ll do my best.

At the moment I’m working on my first Symfony application what will be a toto for the upcoming World Championship soccer. A quite challenging application but fun and instructive.

The design presented me with a great challenge. As you can see in the picture above (which is in Dutch, sorry), we want multiple rows containing the matches of that group per row,  where you can fill in your prediction of the result of that match.

The schema I’ve figured out is this one (just the relevant part) :


Groups:

columns:

id: { type: integer, primary: true, autoincrement: true }

name: { type: string(100) }

Team:

columns:

id: { type: integer, primary: true, autoincrement: true }

group_id: { type: integer, notnull: true }

name: { type: string(255) }

flag: { type: string(255) }

desription: { type: string(4000) }

biography: { type: string(4000) }

points: { type: integer(2) }

relations:

Groups: { local: group_id, foreign: id }

Games:

columns:

id: { type: integer, primary: true, autoincrement: true }

home_team_id: { type: integer, notnull: true }

away_team_id: { type: integer, notnull: true }

stadium_id: { type: integer }

playing_time: { type: timestamp }

desription: { type: string(3000) }

relations:

Stadium: { local: stadium_id, foreign: id, foreignAlias: Stadium }

HomeTeam: { class: Team, local: home_team_id, foreign: id, foreignAlias: homeTeam }

AwayTeam: { class: Team, local: away_team_id, foreign: id, foreignAlias: awayTeam }

Predictions:

actAs: [Timestampable]

columns:

id: { type: integer, primary: true, autoincrement: true }

user_id: { type: integer(4) }

game_id: { type: integer }

home_score: { type: integer(2) }

away_score: { type: integer(2) }

real_home_score: { type: integer(2) }

real_away_score: { type: integer(2) }

relations:

Profile: { local: user_id, foreign: user_id, foreignAlias: User }

Games: { local: game_id, foreign: id, foreignAlias: Game }

Okay, now I’ve shown you the design we wanted and the schema I’ve used. Let me show the implementation I’ve used and the steps how I got to the result.

We want the user to predict the matches that are in the system so I want to show all the games with an embedded form of Prediction. But the problem here is that the 1:n relation is sorted out via the Predictions model, which to me is logical as you have one match with multiple predictions. Here the difficulty came. How do you couple a game to a prediction when there isn’t a relation yet, which is the case in a first prediction.

This is my solution:

I’ve created a new action in the Games controller called executePredict:


public function executePredict( sfWebRequest $request ) {
$this->form = new GamesCollectionForm( );
}

Here we call GamesCollectionForm which collects all games in the system and creates a form for them.


class GamesCollectionForm extends BaseGamesForm

{

public function configure()

{

$this->useFields(array());

$q = Doctrine_Query::create()->from('Games');

$aGames = $q->execute();

$wrapperForm = new sfForm();

foreach( $aGames as $index => $game ) {

$gameForm = new GamesPredictionForm( $game );

$gameForm->widgetSchema->setNameFormat('games[predictions][game_' . $index . '][%s]');

$wrapperForm->embedForm('game_' . $index, $gameForm);

}

$this->embedForm('predictions', $wrapperForm);

}

}

Okay, here I ran into multiple problems. The first one being that the collection form is an child of BaseGamesForm and therefore contains form widgets for adding a game, something I don’t want. So clean them all out by stating that we aren’t going to use any fields.

Next we collect all the games in the system and create an empty sfForm as wrapperform. In this wrapperform we’re going to embed instances of the GamePredictionForm. So for every game we create a GamesPredictionForm and set the right name format. This name format we need for the rendering and saving the forms.

Each form is embedded to the wrapper by setting its index prefixed with “game_”. Eventually the wrapperform gets embedded to the collection form.

The GamesPredictionForm looks like this:


class GamesPredictionForm extends BaseGamesForm

{

public function configure( )

{

$q = Doctrine_Query::create()->from('Predictions')->where('user_id = ? ', sfContext::getInstance()->getUser()->getProfile()->getUserID())->andWhere('game_id = ?', $this->getObject()->getID());

$oPred = $q->fetchOne();

if(! $oPred instanceof Predictions ) {

$oPred = new Predictions();

$this->getObject()->Game[] = $oPred;

}

$this->embedRelation('Game', 'PredictionsForm', array('user_id' => sfContext::getInstance()->getUser()->getProfile()->getUserID()));

$this->widgetSchema['home_team_id'] = new sfWidgetFormInputHidden();

$this->widgetSchema['away_team_id'] = new sfWidgetFormInputHidden();

}

}

What this class does is checking if there is a prediction of the user for this game. If this isn’t the case we create an empty Predictions object so the form renders a Prediction form. The Predictions model gets embedded with the game form and the id widgets are set hidden. This configuration of hidden inputs is needed for the customized rendering as I going to show later.

If we now render our form we got the result like below.

Nice, but not quite what we wanted. Besides that, any user now can edit the stadium, playing time or description. That’s not want we’d like to see, so lets start change the rendering of this form.

Before we change the rendering of the form I’d like to mention that at this moment it isn’t possible to save the form. If you’ll try to save now a database error is thrown stating that you try to enter null values in the games table. Which is correct, we’ve said that we don’t want to use any fields in the GamesCollectionForm. That works for the widgets, not for the save function. We need to modify that. My solution to this problem took me ages to find and I doubt that this is the best way to handle this.

What I’ve done is to override the doSave function of the GamesCollectionForm and checked if there’s any home_team_id been set. If not, the form is used for prediction by a user and we only want to save the embedded forms. In code it looks like this:


public function doSave( $con = null ) {

if( $this->getObject()->getHomeTeamId() != null ) {

} else {

$this->updateObjectEmbeddedForms($this->taintedValues);

$this->saveEmbeddedForms($con);

return;

}

}

What took me so long to figure out is the updateObjectEmbeddedForms. It seems this just don’t happens with the bind function of the form which is called in the action. By that it mean, if the parent values are all null, the embedded forms doesn’t get bound to the form values. I did not tested this so it’s an assumption and not a fact.


public function executePredict( sfWebRequest $request ) {

if($request->isMethod(sfRequest::POST) || $request->isMethod(sfRequest::PUT)) {

$games = Doctrine::getTable('Games')->find(array($request->getParameter('id')));

$this->form = new GamesCollectionForm($games);

$this->processForm($request, $this->form);

} else {

$this->form = new GamesCollectionForm( );

}

}

protected function processForm(sfWebRequest $request, sfForm $form)

{

$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));

if ($form->isValid())

{

$form->save();

$this->redirect('games/predict');

}

}

Well, now we can save our form whether its new or just an update, it works and that’s a victory on its own.

Now the rendering of the form. Our goal is to get the rendering as suggested by the designers. Well I can tell a lot about this, but lets just start by showing the code and the end result of that code. This is the code of the template rendering:


<?php use_helper('Date'); ?>

<h1>Voorspel de wedstrijden</h1>

<form action="<?php echo url_for('games/predict') ?>" method="POST">

<table width="100%">

<?php echo $form['id'];?>

<?php echo $form[$form->getCSRFFieldName() ]->render(); ?>

<?php foreach( $form->getEmbeddedForm('predictions')->getEmbeddedForms() as $pred): ?>

<tr>

<td><?php echo format_datetime($pred->getObject()->getPlayingTime(), 'dd-MM-yyyy HH:mm'); ?></td>

<td><?php echo "<img src='/uploads/flags/" . $pred->getObject()->getHomeTeam()->getFlag() . "' width=15 height=10/>";

echo $pred->getObject()->getHomeTeam(); ?></td>

<td> - </td>

<td><?php echo "<img src='/uploads/flags/" . $pred->getObject()->getAwayTeam()->getFlag() . "' width=15 height=10/>";

echo $pred->getObject()->getAwayTeam(); ?></td>

<td><?php echo $pred->getObject()->getStadium()->getName(); ?></td>

<td><?php echo $pred->getObject()->getStadium()->getCity(); ?></td>

<td><?php echo $pred['Game'][0]['home_score']; ?></td>

<td> - </td>

<td><?php echo $pred['Game'][0]['away_score']; ?></td>

<?php echo $pred['Game'][0]['id']; ?>

<?php echo $pred['Game'][0]['user_id']; ?>

<?php echo $pred['Game'][0]['game_id']; ?>

<?php echo $pred['home_team_id']; ?>

<?php echo $pred['away_team_id']; ?>

</tr>

<?php endforeach; ?>

<tr>

<td colspan="2">

<input type="submit" />

</td>

</tr>

</table>

</form>

Which leads us to…

Hey! It looks like we’ve got it right. In essence this is just like the designers wishes. Well, what’s up with this form rendering…

First of all I start with rendering the hidden form ID tag and the hidden CSFR field. We need this for the binding and security of the form (I’m not sure of the binding part).

Then I want the embedded Predictions form which is the wrapperform containing all forms per match as an embedded form. Hence, the double getEmbeddedForms call. The $pred variable now holds an instance of the GamesPredictionForm class.

Per row we first render all the static match information by retrieving the object and outputting them. After that we render the prediction form elements which are stored in the Game property of the GamesPredictionForm. The game[0] holds the Predictions form which needs to be editable for the user.

That are all the visible fields, they are followed by a bunch of hidden fields so that the objects can be stored right. This information is processed by the bind and updateObject function and are mandatory.

This rendering will be done for every Game stored in the system. And the best thing is that it works!

But there’s a catch. It works and I’m glad with that, but I’m not happy with this solution. I can’t figure out if the model design is incorrect, if I use Symfony wrong or just do something other totally the wrong way around. Therefore I need you! If you got to here you probably have the same problem or a huge interest in Symfony ( or just me, but I’ll let that idea go by ).

Perhaps you can tell me what I can do better to prevent these kind of ‘hacks’ and adjustments. Help me to grow as developer and give your feedback. I appreciate it big time!

Thanks for your time so far!

Als tinyMCE niet in één keer laad

Het wil bij tinyMCE in bepaalde browsers nog wel eens gebeuren dat de editor niet gelijk geladen word. Pas bij een refresh verschijnt de editor. Voornamelijk Safari en google Chrome vertonen dit gedrag.

De oplossing is echter simpel. Controleer of alle plugins die je in de configuratie opgeeft wel bestaan en geladen kunnen worden. Dus deze regel:

plugins : ‘table,advimage,archvr,paste’,

Moet wel over plugins beschikken die bestaan en bereikbaar zijn.

Dit wil ik in 2010 doen

Ok, we zijn al even in 2010 en het is al zeker niet meer de juiste tijd om de “Beste wensen!” te roepen. Overigens toch al een rare zijn.. wat wens je iemand die andere 350 dagen toen dan?

Toch wil ik in dit blog vertellen wat ik dit jaar wil gaan doen op het gebied software engineering, ondernemen en persoonlijk ondernemen. Door het op te schrijven in dit blog kan ik wat feedback van lezers krijgen op de dingen die ik wil gaan doen en ik kan mezelf er ook wat beter aan houden 🙂

Algemene software ontwikkeling

Dit ga ik voornamelijk behalen via het werk wat ik voor school moet doen. Door nu niet alleen maar te lezen over ontwikkelmethodes en software concepten, maar ook verplicht en gestimuleerd te worden door ze te gebruiken hoop ik dat ik dat vrij goed onder de knie krijg.

PHP

Aan het eind van vorig jaar al lichtelijk verdiept in het Symfony framework en dat wil ik zeker nog verder uitbreiden. Symfony biedt interessante mogelijkheden en zorgt voor een stukje zekerheid tijdens het ontwikkelen. Daarnaast helpt het mijn ontwikkeling als programmeur ontzettend. Voornamelijk omdat je gedwongen word op de ´juiste manier´ te programmeren zodat je van alle voordeeltjes gebruik kan maken.

Overigens wil ik niet alleen met symfony werken. Op den duur wil ik ook kennis hebben van andere frameworks zodat je leert te kiezen welk framework je kan gebruiken voor een probleem.

Project methode

Met mijn bedrijf werken voornamelijk aan kleine projectjes van 3 weken. Ik wil op zoek naar een project/ontwikkelingsmethode die ons echt kan helpen om gestructureerd te werken zonder te verzanden in bergen extra werk. Als iemand tips heeft dan lees ik ze graag!

Regular expressions & wiskunde

Dit jaar wil ik erg graag mijn kennis van regexs en wiskunde in zijn algemeenheid verbeteren. Ik merk dat mijn kennis over deze onderwerpen niet optimaal is. Vooral door regex beter onder de knie te krijgen hoop ik wat smerige hacks achterwege te kunnen laten in de toekomst.

Wat betreft Wiskunde heb ik al een boek van Head First gekocht. Dit is voornamelijk bedoeld om te leren sommen te optimaliseren. Soms denk ik gewoon veels te moeilijk.

En verder…

Meer conferenties bezoeken is een belangrijk doel dit jaar en we beginnen goed. Eind januari ga ik naar de conference van PHP Benelux, in april help ik tijdens het pfcongres en de dutch php conference staat ook op het wensenlijstje.

Alhoewel ik niet veel tijd heb lijkt het me erg leuk en zinvol om dit jaar betrokken te raken bij een open source project wat mij aanspreekt. Door met meerdere developers aan een project te werken kan ik een heleboel leren en terug te geven aan de open source community. Dus als iemand een leuk project voor ogen heeft 😉

In ieder geval wil ik nog een heleboel, pas aan het eind van het jaar gaan we zien of het allemaal gelukt is. Heb je nog tips of tricks? Ik hoor ze graag!

Operator Overloading in PHP

Vandaag een groot deel van de dag bezig geweest met het schrijven van een eigen Time class. Dit vanwege een nieuwe applicatie waar ik mee bezig en ik veel calculaties met tijd moet uitvoeren. De class zelf is dus aardig functioneel geworden en met functies als add((), subtract() komt ik al een heel eind, maar wat mij nou mooi leek is dat je $oTime1 – $oTime2 kan doen. Echter dat kan dus niet met PHP.

Na wat onderzoek waarom niet, blijkt dat men dit doet om de leercurve van PHP zo laag mogelijk te houden. Wat mij betreft een wat vreemde uitleg, want volgens mij kan de dynamiek van de taal er juist groter van worden. Is het niet zo dat de verantwoordelijkheid van het gebruik van een functionaliteit bij een gebruiker ligt? Tot hoever moet je als ontwikkelaar van een programmeertaal je gebruikers beschermen tegen fout gebruik van het één en ander. OOP word nu ook genoeg vallen misbruikt, maar dat gaat toch ook niet dicht gezet worden?

Nu ben ik wel benieuwd wat de meningen van anderen hierover zijn.

Opzetten van phpUnderControl op een Windows machine

Deze week ben ik aan het stoeien geweest met het opzetten van phpUnderControl. Het is een mooi pakket wat vrij makkelijk te installeren is, zeker op een linux bak. Op Windows zijn er een aantal aandachtspunten waar je rekening mee moet houden.

Allereerst voor mijn installatie heb ik het volgende artikel van techPortal gevolgd: http://techportal.ibuildings.com/2009/03/03/getting-started-with-phpundercontrol/

Waar je op moet letten

  • De cli commando’s phpdoc, phpcs en phpunit moet je voorzien van .bat anders kan Ant de programma’s niet vinden
  • Alle bestanden omringen door quotes zodat het pad goed gaat
  • Ik heb al mijn svn tags voorzien van –username en -password omdat Ant bij mij wat problemen gaf met het inloggen op de SVN server

Dit zijn mijn aandachtspunten voor het opzetten van phpUnderControl op een Windows bak. Met bovenstaande in gdachten draait het bij mij als een zonnetje inmiddels! Marc Veldman bedankt voor het artikel met heldere uitleg!

Eindelijk, een goede tiny-mce plugin voor word formatting

Al jaren gebruik ik tinyMCE in mijn webapplicaties als Rich Text Editor. Ongeveer dezelfde tijd loop ik tegen het probleem aan dat het voor geen meter met Word formatting om kan gaan. Had ik in de eerste versie nog wel wat regex geplaatst voor Word 2003, vanaf de 2007 versie was dat ook een hopeloos verhaal.

Vandaag toch maar eens tijd gemaakt om op zoek te gaan naar een goede plugin hiervoor en inmiddels ook gevonden! Paste2 is de verlossende redder en verwijdert ook echt alles zonder de semantiek te verliezen! Kudo’s voor de makers van dit project! Nu weer tijd voor eigen ontwikkelingen…