Symfony 3 - EntityType / Dojo - Select Integration

If you use Dojo to provide a polished interface with a Symfony 3 application programmatically, you may use a data- attribute on a select tag to pass the options from Symfony 3 to Dojo.

The entity in this case is phone number type. This is a table with two columns, an id and the type which is a string.

dev=# \d+ phone_number_type
                          Table "public.phone_number_type"
 Column |         Type          | Modifiers | Storage  | Stats target | Description 
--------+-----------------------+-----------+----------+--------------+-------------
 id     | integer               | not null  | plain    |              | 
 type   | character varying(16) | not null  | extended |              | 
Indexes:
    "phone_number_type_pkey" PRIMARY KEY, btree (id)

dev=# select * from phone_number_type;
 id |   type    
----+-----------
  1 | office
  2 | home
  3 | mobile
  4 | alternate
  5 | emergency
  6 | on-call
  7 | other
  8 | security
(8 rows)

The form uses the entity to allow the user to indicate the type of phone number.

PHP

public function buildFormFormBuilderInterface $builder, array $options )
    {
 
        $builder
                ->add'type'EntityType::class, [
                    'class' => 'AppBundle:PhoneNumberType',
                    'choice_label' => 'type',
                    'multiple' => false,
                    'expanded' => false,
                    'required' => true,
                    'choice_translation_domain' => false
                ] )
                ->add'phonenumber'TextType::class, [
                    
                ] )
                ->add'comment'TextType::class )
        ;
    }

Next, the twig block is customized to render the options as a data- attribute on the select tag, rather than the options:

Code

{%- block choice_widget_collapsed -%}
    {%- if required and placeholder is none and not placeholder_in_choices and not multiple and (attr.size is not defined or attr.size <= 1) -%}
        {% set required = false %}
    {%- endif -%}
    <select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple" {% else %} {% endif %}
        {%- if placeholder is not none -%}
            {% set blank_value = (placeholder != '') ? (translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain)) %}
            {% set blank = { 'value': '', 'label': '{{ blank_value|raw }}'} %}
        {%- endif -%}
        {%- if preferred_choices|length > 0 -%}
            {% set choices = preferred_choices|merge(choices) %}
            {%- if choices|length > 0 and separator is not none -%}
                {% set choices = choices|merge([{'label':'{{separator}}','disabled':true}]) %}
            {%- endif -%}
        {%- endif -%}
        {%- set options = choices -%}
        data-options="{{options|json_encode()}}" data-selected="{% if required and value is empty %}{else}-1{% endif %}">
    
{%- endblock choice_widget_collapsed -%}

On the client side, the options are read into a MemoryStore

Code

// select is the select node
        data = JSON.parse(domAttr.get(select, "data-options"));
        // Convert the data to an array of objects
        storeData = [];
        for( d in data ) {
            storeData.push(data[d]);
        }
        memoryStore = new Memory({
            idProperty: "value",
            data: storeData});
        store = new ObjectStore({objectStore: memoryStore});
 
        typeSelect = new Select({
            store: store,
            placeholder: core.type,
            required: true
        }, select);
        typeSelect.startup();

This approach is good for cases where there aren't too many options and they are static. If you had a long list, or wanted an autocomplete, an Ajax solution would be better.

A similar approach may be used with other JavaScript libraries and frameworks.

Symfony 3 - Legacy Authentication Bridge - straight md5

If you are building a system which needs a fallback authentication system to welcome existing users into a new application architecture, you will need to support the authentication approach of the legacy application.

One very common method is to md5 the password and store it in the database. Symfony supports md5 password encoders, but needs a little extra configuration to disable the salt.

This post shows how to configure a connection into the legacy database, chain the authentication providers, and configure the password encoder.

I chose to put all the code that is strictly operating on the legacy system in its own bundle with the rationale that it may be removed in the future. If it isn't removed, it should be easier to set the context of tasks when working in the different bundles.

If you are transitioning from a legacy application to a new architecture, you will probably have two databases - one for the old system and one for the new one. The first step is to set up Symfony and Doctrine to connect to both.

app/config/config.yml

Code

dbal:
        default_connection: default
        connections:
            default:
                driver:   "%database_driver%"
                host:     "%database_host%"
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
                charset:  UTF8
                
            legacy:
                driver:   '%legacy_database_driver%'
                host:     '%legacy_database_host%'
                port:     '%legacy_database_port%'
                dbname:   '%legacy_database_name%'
                user:     '%legacy_database_user%'
                password: '%legacy_database_password%'
                charset:  UTF8
 
     ...
 
    orm:
        auto_generate_proxy_classes: "%kernel.debug%"
        default_entity_manager: default
 
        entity_managers:
            default:        
                auto_mapping: true
                mappings:
                    AppBundle: ~
            legacy:
                connection: legacy
                mappings:                    
                    LegacyBridgeBundle: ~

Then you need to create the user provider as described here http://symfony.com/doc/current/cookbook/security/entity_provider.html. Be sure to make any column name updates and ensure they are reflected in the config files as well.

This is a fallback configuration, with the FOS User Bundle as the first provider (Ref: http://symfony.com/doc/current/cookbook/security/multiple_user_providers.html) and the legacy provider running next.

Add the legacy provider into your chain as follows:

app/config/security.yml

Code

imports:
    - { resource: "@LegacyBridgeBundle/Resources/config/security.yml" }
 
security:
 
    ...
 
    providers:
        chain_provider:
            chain:
                providers: [ fos_userbundle, legacy_user_provider ]
 
    ...
 
        main:
            pattern: ^/
            form_login:
                provider: chain_provider

And set up the encoder the bundle security.yml

security.yml

Code

security:
    encoders:
        LegacyBridgeBundle\Entity\LegacyUser:
            algorithm: md5
            encode_as_base64: false
            iterations: 0
      
    providers:
        legacy_user_provider:
            entity:
                class: LegacyBridgeBundle:LegacyUser
                property: username
                manager_name: legacy

Please note that I'm not recommending unsalted md5 (I do recommend unsalted butter), but sharing an approach that allows the authentication to run against an existing database.

Variable number of parameters on a prepared statement in PHP

Sometimes, you need to use a different number of parameters for a prepared statement. The call_user_func_array function allows you to call bind_param with a varying number of parameters.

PHP

// Start by validating your data
$something filter_input(INPUT_POST'something'FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^(this|that|other|thing)$/i']]);
 
// Set up the types string
$types '';
 
// Initialize the array the variables will be referenced from
$values = [];
 
// Create the base SQL statement
$query 'SELECT * FROM table';
 
// Initialize an array to store the WHERE comparisons
$where = [];
 
// Check to see if $something should be used 
// empty tests for both null (no data in input) and false (invalid data)
if (!empty($something)) {
   
    // Set the WHERE comparison for something
    $where[] = 'something = ?';
 
    // Append the type for this comparison
    $types .= 's';
 
    // Add a reference to the $something variable (that's what the ampersand is)
    $values[] = &$something;
 
}
 
// If the $where array has elements, add them to the query
if (count($where) > 0) {
    $query .= ' WHERE '.implode(' AND ',$where);
}
 
$stmt $mysqli->prepare($query);
 
// Create the array of parameters which will be sent to the bind_param method
$params array_merge([$types],$values);
 
// Bind the variables
call_user_func_array([$stmt,'bind_param'],$params);

What Branch am I on Anyway?

Often during web development the code is switching between different branches and it can be difficult to let everyone on a small team know which branch is in use.

Adding this line to a PHP script and using CSS to make it easy to see can save time.

HTML

<div class="git-branch">'.`git name-rev --name-only HEAD`.'</div>

Be sure to wrap it in some code to prevent display on the production site.

One Approach to Complying with a "script-src 'self'" Content Security Policy

Link: http://www.html5rocks.com/en/tutorials/security/content-security-policy/

I recently encountered this error when working with plugin code on an application:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' chrome-extension-resource:".

The cause of the error was inline script code I was using to pass values from the server to the client.

After a bit of research (see the link above), the best solution looked like a little bit of PHP code to create the JavaScript required to pass the values to the client.

The overhead of checking the timestamp and creating the file is minimal, so this code recreates the JavaScript once each day.

PHP

<?php
 
Class CSP {
    const JSFILENAME 'csp.js';
 
    static public function cspFilename($dir __DIR__) {
        return $dir.'/'.self::JSFILENAME;
    }
 
    static public function cspFileNeedsRebuild($filename) {
        if (!is_file($filename)) {
            return true;
        }
        $fileLastModified date('z',filemtime($filename));
        $today date('z');
        return $fileLastModified !== $today;
    }
}
 
$someValue 'Some value';
$jsFilename CSP::cspFilename();
if (CSP::cspFileNeedsRebuild($jsFilename)) {
    $js 'var someValue = "'.$someValue.'";'.PHP_EOL;
    file_put_contents($jsFilename,$js);
}
echo '<script src="'.$jsFilename.'"></script>';

Other solutions I could have used would have been to disable the Content Security Policy, but that's really a stupid approach. There is also nonce and one may code the policy with more complex values.

:: Next >>