Another Twig Ternary Example

Introduction

Previously in my Simplified Web Development with JSON and the Twig Ternary Operator, I had used JSON that had boolean values. Recently I’ve created another form that has a survey with checkboxes and I stored the JSON with string values instead. This article gives an alternate example of using the Twig ternary operator to handle strings.

The JSON Code

My survey JSON sample looks like the following example:

JSOM_Sample

In the above JSON, the keys represent the survey questions, and the string values represent the checkbox that was chosen. In the above case “strong_A” means “Strong Agree”, and “some_D” means “Somewhat Disagree”. It’s just a simpler way to write/code the JSON and still keep it understandable.

I struggled for a long time on how to handle processing of the JSON data in my Twig template when presenting a view of the form that was submitted. I show the results of the submitted for in a html table with a table row for each question and the selected checkboxes. So one row consists of the question + checkbox1 + checkbox2 + checkbox3 + checkbox4, and a total of 4 questions. So to do this in Twig, I struggle with how to create the for loop.

I thought maybe I could do a Twig for loop using {% for i in 0..3 %} to process each of the 4 checkboxes, and thus I was thinking of using the following JSON code:

JSOM_Sample_Alt

So in the above I would store each checkbox as a boolean in the JSON array, however, this would actually require a lot more Twig code, and a lot of if/else statements. I wanted to simplify my code and reduce the number of lines required.

Problems with Twig If Statements

One way of processing whether any of the checkboxes is selected is by using if/else statements, however, the code gets messy and looks something like the following code sample:

Twig_If_Probs

Notice each html td element has an if/else statement, and a total of 32 lines are used for one table row. This is a lot of code.

Simplifying the Code with the Twig Ternary Operator

I simplified the code by using the Twig ternary operator (as described in the documentation) to replace all the if/else statements. For example, to check my JSON in Twig to see if the “Strongly Agree” checkbox is selected, I use the following code in my html td element:

code

Where in the above “(survey[‘Recommend’] == ‘strong_A’)” does a comparison to see if it matches the string, and if so then show a checked checkbox. The code CheckCode is an html escape code to render a checked checkbox. The code UncheckCode is an empty checkbox.

Example in Twigfiddle

I created a Twigfiddle here to show you the twig working in case you need to see it in action. Also, you will need to see the result in html, so there’s a twig fiddle showing the resultant rendered html in this SyncFiddle.

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.

Setting HTTP Headers in Symfony Crawler Client

Introduction

I’ve recently created a Symfony application that keeps track of the browser that submitted the form. When developing functional tests for this application, I needed to set the HOST and User-Agent HTTP headers on the client. This article is about how to do that.

Creating the Client

All functional test cases should extend WebTestCase in Symfony as described in the testing documentation. So your class should be written like that and each of your tests should include the word “test” in the function name. So an example might be like so:

<?php
// tests/AppBundle/Functional/DefaultControllerTest.php
namespace tests\AppBundle\Functional;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase
{
   public function testLogin(){
   ...
   }
   ...
}

Where the above file is created in the tests\AppBundle\Functional folder. Then since the class extends WebTestCase, now we can create a client like so:

$client = static::createClient();

This just creates the default client, but doesn’t set any specific headers.

Setting the Headers

You may need various HTTP headers for your application. In my case, I needed Host and User-Agent headers. The parameters of the test client is described in the API documentation, and essentially parameter 2 is an array that is the equivalent of a PHP $_SERVER. So in my case I used code like the following:

$client = static::createClient(array(), array(
        'HTTP_HOST' => 'mySymfonyApp', // Set HOST HTTP Header.
        'HTTP_USER_AGENT' => 'Symfony Browser/1.0', // Set Agent header.
));

Notice in the above code, any header is prefixed with HTTP_, and a dash is replaced with underscore. So then User-Agent becomes: HTTP_USER_AGENT

Now with the above code, it will see the client (User-Agent) as Symfony Browser version 1.0.

Hopefully this helps someone out.