How to use filters on custom fields with symfony 1.4

How to use filters on custom fields with symfony 1.4

I recently continued to use my particular relationship that embeds some properties. This time I wanted to add some filters to my student request using the pre-generated filter used in the backend.

Here again, is our example:

N to N relationship with properties
N to N relationship with properties

And the pre-coded filter is here:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
{
}
}
[/php]

Here the filter already includes fields for every field on Student objects (id and name). It is a regular form, so you can override the predefined widgets and validator or even unset some of them. Let’s unset the id:

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure(){
unset($this[‘id’]);
}
}
[/php]

Now we can use our filter like this in our controller:
[php]
// app/frontend/module/student/actions/actions.class.php
class studentActions extends sfActions {
public function executeIndex(sfWebRequest $request) {
//create a default query
$query = Doctrine::getTable(‘Student’)->getQuery();

//obtain the filter values that could have been submited
$filterValues = $request->getParameter(‘student_filters’);
//create the StudentFormFilter using the values, and giving it the query
$this->formFilter = new StudentFormFilter($filterValues);
$this->formFilter->setQuery($query);
//if the form have been submited
if($filterValues){
//bind the values
$this->formFilter->bind($filterValues);
//if valid, specialise the request using the form
if($this->formFilter->isValid())
$query = $this->formFilter->getQuery();
}

//finally get the students
$this->students = $query->execute();
}
}
[/php]

This code could seem a little weird but it is finally all you need to know in order to make a basic use of the auto generated filters. Thus, your template can look like this:

[php]
<?php echo form_tag(url_for(‘student_index’)) ; ?>
<table>
<?php echo $formFilter ; ?>
</table>
<input type="submit"/>
<?php echo ‘</form>’ ?>
// …
<ul>
<?php foreach($students as $student): ?>
<li><?php echo $student->getName(); ?></li>
<?php endforeach; ?>
</ul>
[/php]

Ok, everything is working quite well. But, let’s getting our filter trickier. I want to add a filter that permits me to select students that have a specific enrolment date.

First of all, we need to add a widget and a validator:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
unset($this[‘id’]);

$this->setWidget(‘EnrolmentDate’, new sfWidgetFormFilterDate( array(
‘from_date’ => new sfWidgetFormDate(),
‘to_date’ => new sfWidgetFormDate(),
‘with_empty’ => false)));
$this->setValidator(‘EnrolmentDate’, new sfValidatorDateRange(array(
‘required’ => false,
‘from_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 00:00:00’)
),
‘to_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 23:59:59’)))));
}
}
[/php]

Here, our form will display properly, but the getQuery method used in the will simply ignore our new EnrolmentDate field.

Now the things are getting really uncommon. Symfony is not really built for this kind of behavior, and we will need to override some behavior in our StudentFormFilter to get thing to work. In order to do that I read the sfFormFilterDOctrine.class.php file and more specifically the doBuildQuery function where the request is built. Some of the things I’ll do are a bit ugly but it is the price to pay if we want not to spread our modifications into the core framework, thus I managed to keep everything in the StudentFormFilter.class.php file. This way our ugliness remains contained into a single file and does not impact the framework. So let’s go for it!

First of all we need to add the EnrolmentDate as a new field of the form, to do that we need to override the getFields function:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function getFields() {
return array_merge(parrent::getFields(), array(‘EnrolmentDate’=>’EnrolmentDate’));
}
}
[/php]

The very important thing here is to know that I added a field EnrolmentDate of type EnrolmentDate. It will be crucial later.

Also, the doBuildQuery method will get the Student table and check for the presence of the field EnrolmentDate. The workaround here is to make a stub table and override the getTable function in order to return this stub table.

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function getTable() {
return new StudentStubTable();
}
}

// my stub table
class StudentStubTable extends StudentTable{
public function hasField($field) {
if($field == ‘EnrolmentDate’)
return true;
else
return parent::hasField($field);
}
public function getFieldName($field){
if($field == ‘EnrolmentDate’)
return ‘EnrolmentDate’;
else
return parent::getFieldName($field);
}
}
[/php]

As you can see, two methods need to be overridden in my StudentStubTable class. Those two functions are used by the doBuildQuery function. Here i make the field EnrolmentDate exist and create his fieldName in the table is EnrolmentDate.

From here, all we need to know is that the doBuildQuery function will look for an addEnrolmentDateQuery function in our StudentFormFilter class. This function is called based on the type of the fields that I defined earlier in the getFields method. This call is made by the concatenation of ‘add’ + type of the field + ‘Query’.

So, let’s code the function and make it do our join with the Enrolment table.

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function addEnrolmentDateQuery(Doctrine_Query $query, $field, $values) {
//add our join part
$query->leftJoin(sprintf(‘%s.Enrolment enrolment’,$query->getRootAlias()));

//set the where part depending on the value
if (isset($values[‘is_empty’]) && $values[‘is_empty’])
{
$query->addWhere(‘enrolment.EnrolmentDate IS NULL’);
}
else
{
if (null !== $values[‘from’] && null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
else if (null !== $values[‘from’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
}
else if (null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
}
}
}
[/php]

In this method, we retrieve the query as a parameter and we just have to build our method based on the values we receive. Everything here is about building a proper DQL query.

Now we have a nice filter that can filter on our property embedded into a relationship.

Here is the final code:

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
unset($this[‘id’]);

$this->setWidget(‘EnrolmentDate’, new sfWidgetFormFilterDate( array(
‘from_date’ => new sfWidgetFormDate(),
‘to_date’ => new sfWidgetFormDate(),
‘with_empty’ => false)));
$this->setValidator(‘EnrolmentDate’, new sfValidatorDateRange(array(
‘required’ => false,
‘from_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 00:00:00’)
),
‘to_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 23:59:59’)))));
}

public function addEnrolmentDateQuery(Doctrine_Query $query, $field, $values) {
//add our join part
$query->leftJoin(sprintf(‘%s.Enrolment enrolment’,$query->getRootAlias()));

//set the where part depending on the value
if (isset($values[‘is_empty’]) && $values[‘is_empty’])
{
$query->addWhere(‘enrolment.EnrolmentDate IS NULL’);
}
else
{
if (null !== $values[‘from’] && null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
else if (null !== $values[‘from’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
}
else if (null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
}
}

public function getFields() {
return array_merge(parrent::getFields(), array(‘EnrolmentDate’=>’EnrolmentDate’));
}

public function getTable() {
return new StudentStubTable();
}
}

// my stub table
class StudentStubTable extends StudentTable{
public function hasField($field) {
if($field == ‘EnrolmentDate’)
return true;
else
return parent::hasField($field);
}
public function getFieldName($field){
if($field == ‘EnrolmentDate’)
return ‘EnrolmentDate’;
else
return parent::getFieldName($field);
}
}
[/php]

If you have any remarks about my implementation (I am sure you have) don’t hesitate to tell me about, I think this code can still be improved.

Advertisements

Add properties to a relationship – Dealing with forms in symfony 1.4

Add properties to a relationship – Dealing with forms in symfony 1.4

Lately, I showed how you can a simply add properties to a relationship:

Doctrine 1.2: Add properties to a relationship

I continued to use this solution until I recently faced some issue for it. In fact, I discovered what I thought was a weird behavior until I completely understood the full impact of the solution.

Let’s take back our example:

N to N relationship with properties
N to N relationship with properties

I am using it in the symfony Framework, and I want to make a student form that includes also the enrolment properties. To do that, I can just use the embedded relation:

[php]
class StudentForm extends BaseStudentForm {
public function configure() {
$this->embedRelation("Enrolments");
}
}
[/php]

This way, symfony automatically takes care about including the collection of enrolments into my form. And process it after the submission.

You can simply include it in your template:

[php]
$form = new StudentForm();
echo $form;
[/php]

This is working pretty well!

The matter is that I want to customize my form, and thus I template it:

[php]
<form action="./submit" method="post">
<table>
<tbody>
<tr>
<th>Name</th>
<td><?php echo $form[‘name’]->render() ?></td>
</tr>
<?php /* Include the collection of enrolments */ ?>
<?php foreach($form[‘Enrolments’] as $enrolment): ?>
<tr>
<?php echo $enrolment[‘enrolementDate’]->render() ?>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<input type="submit" value="Save" />
</td>
</tr>
</tfoot>
</table>
<?php /* Don’t forget hidden fields */ ?>
<?php echo $form->renderHiddenFields() ?>
</form>
[/php]

Now, I have a fully customizable form… but it is not working when submit. Worse, it erases your enrolments!!! If you look into the doctrine log, I can see that you have a DELETE statement that should not be here. And then the update is done on your enrolments, even if they have been deleted.

At first, it is very strange. The reason why, is because when you embedded the enrolment relationship into your form, you also have indirectly embedded the course relationship. This is because the enrolment carries the course relationship. When symfony processes the form, it sees that you have enrolments, but you don’t have the courses that they should be bound to. So, it must be that your removed the course, and thus, the enrolments have no reason to exist anymore.

In order to work around this issue, you simply have to add the course into your form. I just did it with hidden inputs:

[php]
<?php foreach($form->getObject()->getCourses() as $course): ?>
<input type="hidden"
name="student[courses_list][]"
value="<?php echo $course->getid() ?>">
<?php endforeach; ?>
[/php]

Now, when symfony processes the form, it finds the courses ids and keeps the relationship intact.

You now have a good working form!

Newbie mistake in Silverlight UI threading

Newbie mistake in Silverlight UI threading

I just began my first development with Silverlight for windows mobile 7 and I rapidly ran into one of the most famous newbie mistake: the misuse of the UI threading.

At first I just wanted to make a basic app that load a webpage and display the string content. Thus a simple default project in which I add a simple text block is just enough:

A basic text block into the default layout
A basic text block into the default layout

My view is ready; let’s add some code to it. I just add ContentPanel_Loaded event and insert a simple http request that will load a webpage and update the text label:

[csharp]
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}

private void ContentPanel_Loaded(object sender, RoutedEventArgs e)
{
// instantiate the web client
var client = new RestClient("http://google.fr&quot;);
// make the asynchronous request, assign the result to the textblock
client.ExecuteAsync(new RestRequest(Method.GET),
r => textBlock1.Text = r.Content);
}
}
[/csharp]

Actually I used the RestSharp library (RestSharp website) in order to easily make a asynchronous request. The important thing is that I threaded my request and when I get the result I display it in the textblock.

At first, it looks like it could work well but… when you run it throw a UnathorizedAccessException (“Invalid cross-thread access”). Basic mistake.

We need to know that Silverlight support multithreading but reserve the UI access to one specific thread. When we made the asynchronous request, it was in another thread that has no right to update the UI, thus we got the previous exception.

In order to fix this, we need to access to the authorized thread and make it update the textBlock.

[csharp]
private void ContentPanel_Loaded(object sender, RoutedEventArgs e)
{
var client = new RestClient("http://google.fr&quot;);
client.ExecuteAsync(
new RestRequest(Method.GET),
r =>
{
// access the dispatcher and give the action
Dispatcher.BeginInvoke(
new Action<string>(UpdateTextBlock),
r.Content);
});
}

// function that update the textBlock
void UpdateTextBlock(string result)
{
textBlock1.Text = result;
}
[/csharp]

Now, instead of directly update the textBlock, we tell the dispatcher to call the UpdateTextBlock function with the request string result. The dispatcher will automatically ask the UI thread to call the function, thus it is executed by the right thread.

The result in the emulator
The result in the emulator

You can find more information about this subject here: Dispastcher documentation.