Create Symfony Cookie and set HttpOnly to false

Introduction

Recently, I tried to get cookies from the DOM using the following code:

var cookies = document.cookie.split('; ');

I noticed every time, the “document.cookie” value was a blank string, and I was scratching my head trying to figure out how such a thing could happen.

Then I happen to stumble upon that there is a “HttpOnly” parameter that can be set and this was the root cause.

HttpOnly Usage

So what’s the purpose of the HttpOnly flag? According to this Coding Horror post, is was introduced in the IE6 SP1 browser as part of a plan to reduce XSS. Actually, it is really a good idea, since with the HttpOnly flag set to “true”, any Javascript code will not be able to access the cookie.

Symfony Defaults

Unfortunately the Symfony cookie defaults to “true”. In my application I was creating a cookie something like this:

$cookie = new Cookie(
   'my_cookie',    // Name.
   $obj->getId(),  // Value.
   time() + ( 2 * 365 * 24 * 60 * 60) // Expires 2 years.
);

If you look at the Symfony Cookie construct documentation, you’ll see the default of $httpOnly is “true”; so given the above code, the HttpOnly flag will be set to true.

Create with HttpOnly set to False

If you need to set the HttpOnly flag to false, you’ll need to code something like this:

$cookie = new Cookie(
   'my_cookie',
   $obj->getId(),
   time() + ( 2 * 365 * 24 * 60 * 60),
   '/', // Path.
   null, // Domain.
   false, // Xmit secure https.
   false // HttpOnly Flag.
);

Some values of the above I just set to common sense values, and in particular the HttpOnly flag is set. Once this is done, you can now do things like delete the cookie or change it as needed.

Hope this helps someone out.

Reducing KnpPaginatorBundle Database Queries

Introduction

If you are not familiar with the KnpLabs KnpPaginatorBundle and how to use it with Symfony applications, you might want to read up on it. The GitHub page does say it is a Symfony 2 bundle, however, it does work perfectly with Symfony 3.

Why would you use it? If you want to have pages where you show all of your database Entity results, this is a perfect solution, since instead of showing all the results, it shows pages of results. You would simply click through the pages of results, also columns are sortable.

I have some applications that use this bundle, and just recently noticed that a large number of database queries were being made on one page, which is not acceptable in a production environment. This article is about how I found that out and what I did to significantly improve performance.

Symfony Debug URL

Most newbies that come from a CakePHP or Drupal environment (and true newbies) are not at all familiar with the Symfony debug URL. To access the debug URL, you simply append “app_dev.php” after the hostname followed by the route. Let’s say you had a route in your controller called “home”, and your hostname was called “example.com”, then you would use the following debug URL:

http://example.com/app_dev.php/home

At the bottom of the page you will now see the debug URL like the screenshot below:

Debug_URL

In the above screenshot, the green 200 shows it got a 200 status code, and the “@ homepage” indicates the route retrieved is “homepage”. The “8 in 15.82 ms” indicates 8 database queries in 15.82 milliseconds. On the far right is the version of Symfony (3.2.8), and you can click on the big “X” to close the debug toolbar.

Too Many Queries

On my submitted route I noticed the color of the database queries section was yellow, which normally indicates a warning. It looked like the following:

submitted_excess

If you hover over the database queries you will also see more details. Below is a screenshot of the full page with data obfuscated so you can see the paginator in action and what is to be expected:

Excess_DB_queries

If you actually click on the database queries section of the debug toolbar, it will bring up the Doctrine debug menu showing all the queries that were run and details. This is how I figured out where the excessive queries were coming from (more below).

Original Query

My original query simply selected all columns in my Application and Student Doctrine Entities, which at first I thought was ok, but later realized was the cause of the excess queries. Here is a screenshot of the original Doctrine query in my Eclipse IDE:

Unmodified_Query

I query the Application Entity as “appl” alias, and then join Student on “app_student” which is a OneToOne JoinColumn in Doctrine. The problem with querying all columns of an Entity, is that it will try to get all properties of the Entity, including joined Entities.

Required Columns

At first I wasn’t sure how to optimize my query, but then I realized I only need to get what was used in my Twig file to render the page. I needed the Student id, first & last names, and ban_id, plus the Application signature. So I first made my select in my Doctrine Query Builder like so:

$dql->select('s.stu_id,s.l_name,s.f_name,s.ban_id,appl.stu_sig')

However, if you do something like that, you mos likely will get an error:

Cannot select entity through identification variables without choosing at least one root entity alias.

This is because “app.app_id” the Application identifier needs to be selected from the alias. I then came up with this final query:

Optimal_Query

Then when I render the page, you can see the significant improvement:

Optimized_DB_queries

So the number of queries went from “64” down to “4”, which is incredibly significant.

Lesson learned: Make sure you optimize your Doctrine queries, as they may not be performing exactly as you expect.

Enjoy!

Displaying a Longer Description When Hovering Over a Drop-Down List Item in Symfony

Introduction

In a form I’ve recently created, it has a “Program Type” drop-down list, and in that list I have only abbreviated keys and values. I wanted to make it so that when you hover over the list item, that you see a longer description of what the value is. This article describes how I did that.

Drop-Down List Code

When using the Symfony PHP framework, you use a ChoiceType Field type in your FormBuilderInterface for a drop-down list. The “expanded” and “multiple” determine what type of choice widget it is. Here is a screenshot of my Eclipse IDE of what the code looks like:

Prog_Type_code

Notice I use the “choice_attr” option, which can be callable (a function). In my case, the code checks the what the key is set to and sets the return value to the appropriate string. I used the html title attribute to achieve the hover functionality.

Resultant Rendering

In my Twig code, I simply render the field like so:

{{ form_label(form.program) }} {{ form_widget(form.program) }}

Which just renders the label and the widget. When hovering over one of the items in the drop-down list it will look like the following screenshot:

Hover_Title_Choice_Attr

Notice in the above case, the key is equal to “Cert Achieve”, the “choice_attr” function checks that it is equal to that and sets the html title attribute to “Certificate of Achievement”. That is the text that is shown when hovering.

Hopefully this helps someone out with figuring out the “callable” options in Symfony field types.

Proper Doctrine Annotation Spacing

Introduction

I was recently working on a console application in Symfony and I had an Entity file called “Record.php”. Whenever I tried to run:

php bin/console doctrine:generate:entities AppBundle/Entity/Record

I would always get a Doctrine\ORM\MappingException Class “AppBundle\Entity\Record” is not a valid entity or mapped super class. error as shown in the following screenshot:

Doctrine_MappingException

Annotation Spacing Problem

I know I had the correct Doctrine annotations specified, since I referred to working Entities in other projects, so I was baffled as to what could be the problem. I decided to change the indentation of the @ORM\Entity annotation. This fixed the problem.

Apparently, if you have a tab instead of a single space, the annotation does not get recognized. I had a similar problem that I noticed with the Symfony Route annotation.

The way to see the problem is to turn on whitespace visibility in the IDE you use. In Eclipse, you would go to “Window > Preferences” and then view under “General > Editors > Text Editors” and select the checkbox “Show whitespace characters”. The tab will look like this:

Eclipse_whitespace

Change the tab to space and then rerun the “doctrine:generate:entities” and it should work for you.

Hopefully this will help someone out! I struggled for a while on this.

Doctrine Entity and MySQL Reserved Words

Introduction

Recently for an app I’ve been creating I had to create a ExitForm Doctrine Entity. So I simply created the following code:

<?php

// src/AppBundle/Entity/ExitForm.php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name="exit")
*/
class ExitForm
{
   ...

Then after that I ran the standard Symfony commands to generate getters/setters and update the database:

php bin/console doctrine:generate:entities AppBundle/Entity/ExitForm
php bin/console doctrine:schema:update --force

Everything looks fine an ran without any errors

Checking on the MySQL Database

Then, I went into MySQL and ran the following commands:

show tables;

It showed all my tables, and it showed “exit” as one of the tables. Then I ran this command:

describe exit;

It gave me errors. I struggled for a awhile trying to figure out what the problem was, but then I figured it out, “exit” is a MySQL Reserved Word. So instead of using that I changed my code to this:

/**
* @ORM\Entity
* @ORM\Table(name="exitform")
*/
class ExitForm
{

Everything worked correctly after that. This is just a lesson never to use a Reserved Word.

 

Survey Form in Symfony

Introduction

I’ve recently had to develop a form in a Symfony application that has a section of it with survey type questions. In Symfony it’s not so obvious that the ChoiceType field type is the most suitable type, but it definitely is the one to use. This article describes how to create the survey form.

Original Requirements

I didn’t have any written down requirements other than the existing form, and below is a screenshot of what that looks like:

original_survey

As you can see, it uses checkboxes; which is not appropriate. The reason being is that normally in a group of checkboxes, you can check any or all of them. In a survey, you only want to check one of them. So a radio button is the best choice.

Also look closely at the headings. The problem is the order. The order should be from one extreme to another. So instead this order:

Strongly Agree — Somewhat Agree — Somewhat Disagree — Strongly Disagree.

So my form would need to incorporate those need requirement changes.

ChoiceType in Form Class

In my form class, I initially used the following sample code for the “recommend” set of radio button choices:

->add('sur_recommend', ChoiceType::class, array(
    'mapped' => false,
    'label' => 'I would recommend this program to my friends and '.
               'family as a great opportunity',
    'choices' => array(
            'strong_A' => 'strong_A',
            'some_A' => 'some_A',
            'some_D' => 'some_D',
            'strong_D' => 'strong_D',
    ),
    'expanded' => true,
    'multiple' => false,
))

With the two settings “expanded” set to true and “multiple” set to false, this sets the choice group to radio buttons. The “mapped” set to false just means that “sur_recommended” is not a Doctrine database column, so the data is not mapped. I keep the values and the keys the same, since I don’t want to show any labels in my case.

Unfortunately with the above code, it doesn’t quite work for my case. If I render in Twig using just this code:

{{ form_widget(form.sur_recommend) }}

Then the resultant output will render like the following screenshot:

buttons_w_labels

I don’t want those labels appearing at all, but instead in a column header on the top.

Fixing the Code

In order to prevent the labels from showing, you should set “choice_label” to false, like the following code:

->add('sur_recommend', ChoiceType::class, array(
    'mapped' => false,
    'label' => 'I would recommend this program to my friends and '.
               'family as a great opportunity',
    'choice_label' => false,
    'choices' => array(
            'strong_A' => 'strong_A',
            'some_A' => 'some_A',
            'some_D' => 'some_D',
            'strong_D' => 'strong_D',
    ),
    'expanded' => true,
    'multiple' => false,
))

Then when rendered the labels no longer appear. The other problem is, we have a label for the choice group, but wee just want it to appear on the left side, and the buttons on the right. We also need the buttons to each appear under the names of each of the column headings. This needs to be done in Twig, which is described next.

Using Twig to render Properly

Twig is a powerful rendering language, and it can greatly simply any of your programming efforts. So I highly recommend learning how to use it properly. You can always use Twigfiddle to experiment first with code that you need to write and also to help you learn.

Since I don’t render each of the choice values (which would be labels), I need to put a header column on the top, and put each of the choice group labels on the left. Then each of the radio buttons need to line up under the header columns. To do this, a html table is a good idea.

Here is the Twig code that I came up with:

<fieldset>
<legend>Survey:</legend>
   <table>
      <tr><td></td><td>Strongly Agree</td><td>Somewhat Agree</td>
          <td>Somewhat Disgree</td><td>Strongly Disgree</td></tr>
      <tr style="text-align: center;"><td>{{ form_label(form.sur_recommend) }}</td>
         {% for i in 0..3 %}
            <td>{{ form_widget(form.sur_recommend[i]) }}</td>
         {% endfor %}
      </tr>
      ...

   </table>
</fieldset>

Notice the heading column is in one row, and I have a td element for each heading. I use the form_label to show the choice group label on the left. Notice I have a for loop, and a td element for each radio button as part of the choice group.

The resultant code renders on a page as follows:

final_survey

This is quite functional and looks good and similar to the original.

Testing Symfony Forms with Complex Field Types

Introduction

Creating functional tests for controllers that have forms with complex field types can be hard to understand at first, but once you understand the basic concepts, they become easier. This article explains a simple example that I had to test with an application i developed.

Client Crawler and Redirects

For my application, I needed to click on a link to get to the authentication page, and then after authentication succeeds, that controller redirects back to the homepage (with cookies created). Since a typical test case looks for a 200 response code, when redirected, you will actually get a 302 response code instead. To workaround this in Symfony, you’ll need to follow redirects. Sample client code would look like the following:

...
public function testLogin(){
    $client = static::createClient(); // Create crawler client.
    $client->followRedirects(); // After authenticating will redirect.
    ...

So in the above code, the followRedirects() method is used when your client is expecting to be redirected on a successful response as per the documentation.

Getting Form Page and Node

After creating the client, I needed to click a link to go to the login page and then assert that it is the correct page and get a crawler node (the login button). Example code is as follows:

...
$crawler = $client->request('GET', '/'); // Get homepage.
$loginLink = $crawler->selectLink('Login link')->link();
$crawler = $client->click( $loginLink ); // Click link.

$this->assertContains(
    'Enter your details',
    $client->getResponse()->getContent()
);

$authCrawlerNode = $crawler->selectButton('Login'); // Get form button.
...

The above code gets the homepage content, looks for a link called “Login link” then clicks it; and then finally asserts the resultant page contains “Enter your details”. Then it gets the crawler node a button labelled “Login”.

The Login Form

My form is interesting in that it asks for first & last names and the person’s date of birth. See the screenshot below:

Authenticate

Notice the date of birth is a drop-down list. This is a complex type of input I was mentioning at the beginning of this article.

The Symfony testing documentation only briefly describes how to perform testing on some complex form input types, so you really have to experiment to figure things out. For example, in my case, based on the documentation I could write the following example code:

...
$form = $authCrawlerNode->form(); // Get the form.

// Fill in the Authentication form details.
$form['form[f_name]'] = 'John';
$form['form[l_name]'] = 'Doe';
$form['form[dob][day]']->select('2');
$form['form[dob][month]']->select('7');
$form['form[dob][year]']->select('1981');
...

However, that’s not the easiest way to write the code.

You may wonder how I figured out the form input names to use and the values. If you use FireFox, right-click on the day drop-down list element and select “Inspect element” and you’ll see something like the following:

dob_day_inspect

Notice the name value. You can copy and paste that into your code. The name is a composite of the form name + form field type name + list item. So in my case, my form was called “form”, and the field type name is “dob”, and the list item is the “day” field.

Easier Way to Write Code

An easier way to write the code is put the values you want to enter in the form in an array, and use the crawler node to do this like so:

...
$form = $authCrawlerNode->form(array(
      'form[f_name]' => 'John',
      'form[l_name]' => 'Doe',
      'form[dob][day]' => '2',
      'form[dob][month]' => '7',
      'form[dob][year]' => '1981'
));

$crawler = $client->submit( $form );
...

Notice you don’t call the select method on the drop-down list items, but just enter the correct values you need.

You can simplify even further by just using similar code and the client submit method like so:

...
$client->submit( $authCrawlerNode->form(array(
     'form[f_name]' => 'John',
     'form[l_name]' => 'Doe',
     'form[dob][day]' => '2',
     'form[dob][month]' => '1',
     'form[dob][year]' => '1981'
)));
...

Verifying Submission

Finally after submitting the form, you need to assert that the user was indeed authenticated. The code would look like the following:

   ...
   $client->submit( $authCrawlerNode->form(array(...
   ...

   $this->assertEquals(200, $client->getResponse()->getStatusCode());
}

Since the follow redirects has been enabled, then then expected response code of 200 should be expected. You should verify any failures of the test case. A lot of times it will be the incorrect array values specified. All the above code is from working real examples.

I hope this is a good example for someone to use.