Why companies should stop supporting single platform browsers for their webaaps

Lately I’ve seeing an increasing interest from companies to create responsive applications instead of native ones. I’m not talking about websites targeted to marketing and build their presence on the web, but “WebApps” apps that the company use for its daily operation.

This applications try to provide a user experience as close as possible as native applications while allowing freedom of platform, device and client.

I strongly believe this is the way to go for a business seeking improve and expand their operations, of course, there will be cases when WebApps are not suitable for the task, but for those it does, companies instantly benefit from their nature, just to mention a few:

  • Are written in HTML, JS and CSS all well known languages easy to use and to find programmers.
  • With a single code-base support a variety of platforms and browsers, so there is no need for a team for every specific platform.
  • Related to the previous, implementing new features and fixing bugs gets easier as it only have to happen once.
  • Are able to optimize app presentation according to various screen sizes.
  • Are more time proof as technologies implied are very stable and settled.
  • Simplify infrastructure, you only need a browser to get your users going.

Is clear the moving to the web is an effort to become platform independent, portable,  a more standardized way to deliver content and reach your audience making efficient use of your resources.  Therefore, I believe that having to support single-platform browsers like Internet Explorer/Edge and Safari etc. defeats the whole point of what WebApp is all about and an step back to the old vicious circle.

This might sound radical, but is not uncommon for companies to run custom versions of an operating system or an specific IM client or X antivirus, because when it comes to company’s software standardization is not an option.

Don’t get me wrong I support and recognize that the diversity of implementations is good for the industry and we’ve all been witnessed clever solutions fight its way to standards or being pioneers of a new feature.

But when it comes to business applications, restricting your employees to any of the major cross-platform browsers won’t be any different than forcing them to use a company email for internal communications, after all, keep in mind that this apps are intended for company’s day to day operation.

Dead simple default values on Symfony2 forms

Last day I wanted to set the default values for text and choice  type fields in a project and it came out that I couldn’t find any reference about this in the docs.  While digging on other  blogs for tips I found various methods but they all required an entity for it!! (I felt that this was an overkill solution for what I wanted to do).

I just needed to add a simple text and select (choice) field that really had not enough semantic meaning to justify creating a new entity each.

So my first attempt was trying to set it on Type definition like this:

$builder
->add('name', 'text', array(
    'attr' => array('value' => '<type a name>')    
))
->add('favNumber', 'choice', array(
    'choices' => array(0, 1, 2, 3, 4, 5)
));

Then on the controller

$entity = new MyEntity();
$form   = $this->createForm(new MyEntityType(), $entity);

I expected this to work as with labels and set the default value to “<type a name>” when the entity didn’t had a value and if it did then use the entity’s value.  And it did somewhat worked but the problem with this approach is that it always sets the value to “<type a name>” regardless if the entity had a value or not.  So for the edit form this wasn’t going to cut it, and using javascript  on document load() for this was unacceptable.  Also similar approach didn’t worked at all for choice fields.

So after a while I found that simply setting the default value on the Entity class worked well for both text and choice types.

// Entity
class Entity {
    private $name = '<type a name>';
    private $favNumber = '3';
    ...
 }

// Type
$builder
->add('name', 'text')
->add('favNumber', 'choice', array(
    'choices' => array(0, 1, 2, 3, 4, 5)
));

// Controller
$entity = new MyEntity();
$form   = $this->createForm(new MyEntityType(), $entity);

AHA! and this works as expected on edit forms as well.

// EXTRA
Sometimes you need to provide default values different than the defaults ones (you know what I mean), in that case (except for choice types) twig provides a convenient way to work this out:

{{ form_widget(form.name, {'attr': {'value': '<Full Name>'}}) }}

You can use this method to override any attribute or check checkboxes.

And that’s it, hope this prove useful and helps you become more productive.

How to FOSUser + FOSFacebook + Custom Login

In this article I’m going to teach you how to integrate FOSUser + FOSFacebook to enable your application login by using facebook or your custom login at the same time …

This took me a lot of time, but once you understand it is pretty easy, in sake of brevity I’ll show you only the key points and you should do the rest, ok lets do this!!

Important: this works for Symfony 2.1 only as it depends on chained providers feature, for Symfony 2.0 a somewhat different setting is needed.

Lets keep it as simple as possible so start with a fresh new download of Symfony Standard Vendors package, set it up so you can run the Acme/DemoBundle, we will be using this as a base.

Ready? Ok, you’ll note that on /demo/secured/login the demo provides us a custom login which uses a memory provider. This provider is what you want to replace in order to persist/provide users: e.g. FOSUserBundle provider (checkout their documentation here for further details). The Demo app provided has 3 controllers named DemoController.php SecuredController.php, WelcomeController.php, we are only interested on SecuredController.php. This controller protects /demo/secured/hello action against unauthenticated users. For convenience lets tweak the security.yml a bit so that when we login and logout it takes us back to the login page instead of the welcome page. Replace your “main” firewall with the following

  main:
      pattern:    ^/demo/secured/
      form_login:
          check_path: /demo/secured/login_check
          login_path: /demo/secured/login
          default_target_path: /demo/secured/hello/world
      logout:
          path:   /demo/secured/logout
          target: /demo/secured/login

Install FOSUserBundle and FOSFacebookBundle.

Ok, now install FOSUserBundle and FOSFacebook on your app.

Add the dependencies on composer.json

 "require": {
         ...
        "friendsofsymfony/user-bundle": "*",
        "friendsofsymfony/facebook-bundle": "dev-master",
         ...
}

Download them by running these commands

$ php composer.phar update friendsofsymfony/user-bundle
$ php composer.phar update friendsofsymfony/facebook-bundle

Lastly enable the bundles on appKernel.php

 
    public function registerBundles() { 
        return array( 
            // ... 
            new FOS\UserBundle\FOSUserBundle(), 
            new FOS\FacebookBundle\FOSFacebookBundle(), 
            // ... 
        ); 
    }

Enable Facebook library on your pages.

Lets add the facebook library loader, handlers and the login button. Replace your Acme/DemoBundle/Resources/views/layout.html.twig with the following.

<!DOCTYPE html>
<html lang="en" xmlns:fb="http://www.facebook.com/2008/fbml">
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Demo Bundle{% endblock %}</title>
        <link rel="icon" sizes="16x16" href="{{ asset('favicon.ico') }}" />
        <link rel="stylesheet" href="{{ asset('bundles/acmedemo/css/demo.css') }}" />
    </head>
    <body>
        <script>
          function goLogIn(){
              window.location = "{{ path('_fb_security_check') }}";
          }

          function onFbInit() {
              if (typeof(FB) != 'undefined' && FB != null ) {
                  FB.Event.subscribe('auth.statusChange', function(response) {
                      setTimeout(goLogIn, 500);
                  });
              }
              FB.Event.subscribe('auth.logout',
                       function(response) {
                           if (response.status === 'unknown')  {
                              window.location = "{{ path('_demo_logout') }}";
                            }
              });                            
          }
        </script>
        {{ facebook_initialize({'xfbml': true, 'fbAsyncInit': 'onFbInit();'}) }}
        <div id="symfony-wrapper">
            <div id="symfony-header">
                <a href="{{ path('_welcome') }}">
                    <img src="{{ asset('bundles/acmedemo/images/logo.gif') }}" alt="Symfony logo" />
                </a>
                <form id="symfony-search" method="GET" action="http://symfony.com/search">
                    <label for="symfony-search-field"><span>Search on Symfony Website</span></label>
                    <input name="q" id="symfony-search-field" type="search" placeholder="Search on Symfony website" />
                    <input type="submit" value="OK" />
                </form>
            </div>

            {% for flashMessage in app.session.flashbag.get('notice') %}
                <div>
                    <em>Notice</em>: {{ flashMessage }}
                </div>
            {% endfor %}

            {% block content_header %}                
                <ul id="menu">
                    {% block content_header_more %}
                        <li><a href="{{ path('_demo') }}">Demo Home</a></li>
                    {% endblock %}
                </ul>

                <div style="clear: both"></div>
            {% endblock %}

            <div>
                {{ facebook_login_button({'autologoutlink': true}) }}
                {% block content %}
                {% endblock %}
            </div>

            {% if code is defined %}
                <h2>Code behind this page</h2>
                <div>{{ code|raw }}</div>
            {% endif %}
        </div>
    </body>
</html>

One important thing to note on the above file is that the function goLogIn() redirects to _fb_security_check, this route doesn’t need an implementation as the firewall handles it, BUT it has to be defined and also has to be behind the firewall (“main” in our case) so lets add it to our routing.yml

#route has to be different form the other authetication providers
 _fb_security_check:
  pattern: /demo/secured/facebook/login_check

The User Class

In our app when a user log’s in they are persisted into database (if doesn’t exists) so we can build up our users database. Lets create the users Entity

<?php
// Acme/DemoBundle/Entity/User.php
namespace Acme\DemoBundle\Entity;

use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="user")
 */
class User extends BaseUser
{
    /**
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="firstname", type="string", length=255)
     */
    protected $firstname;

    /**
     * @var string
     *
     * @ORM\Column(name="lastname", type="string", length=255)
     */
    protected $lastname;

    /**
     * @var string
     *
     * @ORM\Column(name="facebookId", type="string", length=255)
     */
    protected $facebookId;

    public function serialize()
    {
        return serialize(array($this->facebookId, parent::serialize()));
    }

    public function unserialize($data)
    {
        list($this->facebookId, $parentData) = unserialize($data);
        parent::unserialize($parentData);
    }

    /**
     * @return string
     */
    public function getFirstname()
    {
        return $this->firstname;
    }

    /**
     * @param string $firstname
     */
    public function setFirstname($firstname)
    {
        $this->firstname = $firstname;
    }

    /**
     * @return string
     */
    public function getLastname()
    {
        return $this->lastname;
    }

    /**
     * @param string $lastname
     */
    public function setLastname($lastname)
    {
        $this->lastname = $lastname;
    }

    /**
     * Get the full name of the user (first + last name)
     * @return string
     */
    public function getFullName()
    {
        return $this->getFirstName() . ' ' . $this->getLastname();
    }

    /**
     * @param string $facebookId
     * @return void
     */
    public function setFacebookId($facebookId)
    {
        $this->facebookId = $facebookId;
        $this->setUsername($facebookId);
        $this->salt = '';
    }

    /**
     * @return string
     */
    public function getFacebookId()
    {
        return $this->facebookId;
    }

    /**
     * @param Array
     */
    public function setFBData($fbdata)
    {
        if (isset($fbdata['id'])) {
            $this->setFacebookId($fbdata['id']);
            $this->addRole('ROLE_FACEBOOK');
        }
        if (isset($fbdata['first_name'])) {
            $this->setFirstname($fbdata['first_name']);
        }
        if (isset($fbdata['last_name'])) {
            $this->setLastname($fbdata['last_name']);
        }
        if (isset($fbdata['email'])) {
            $this->setEmail($fbdata['email']);
        }
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
}

The User provider

We also need a user provider, checkout Symfony documentation on user providers, for now just know that it has the responsibility to retrieve and return a user, if it doesn’t exists then it stores it as well.

<?php
// Acme/DemoBundle/Security/User/Provider/FacebookProvider.php

namespace Acme\DemoBundle\Security\User\Provider;

use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use \BaseFacebook;
use \FacebookApiException;

class FacebookProvider implements UserProviderInterface
{
    /**
     * @var \Facebook
     */
    protected $facebook;
    protected $userManager;
    protected $validator;

    public function __construct(BaseFacebook $facebook, $userManager, $validator)
    {
        $this->facebook = $facebook;
        $this->userManager = $userManager;
        $this->validator = $validator;
    }

    public function supportsClass($class)
    {
        return $this->userManager->supportsClass($class);
    }

    public function findUserByFbId($fbId)
    {
        return $this->userManager->findUserBy(array('facebookId' => $fbId));
    }

    public function loadUserByUsername($username)
    {
        $user = $this->findUserByFbId($username);

        try {
            $fbdata = $this->facebook->api('/me');
        } catch (FacebookApiException $e) {
            $fbdata = null;
        }

        if (!empty($fbdata)) {
            if (empty($user)) {
                $user = $this->userManager->createUser();
                $user->setEnabled(true);
                $user->setPassword('');
            }

            // TODO use http://developers.facebook.com/docs/api/realtime
            $user->setFBData($fbdata);

            if (count($this->validator->validate($user, 'Facebook'))) {
                // TODO: the user was found obviously, but doesnt match our expectations, do something smart
                throw new UsernameNotFoundException('The facebook user could not be stored');
            }
            $this->userManager->updateUser($user);
        }

        if (empty($user)) {
            throw new UsernameNotFoundException('The user is not authenticated on facebook');
        }

        return $user;
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$this->supportsClass(get_class($user)) || !$user->getFacebookId()) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        return $this->loadUserByUsername($user->getFacebookId());
    }
}

Weeoof!! those were a few lines of code don’t they? keep strong the tough part is over.  Now we need to configure FOSUser so it can access the database, lets tell it to use Doctrine to persist and query our users.  Add at the end of config.yml

Tie up everything:

fos_user:
    db_driver: orm
    firewall_name: facebook
    user_class: Acme\DemoBundle\Entity\User

Here we also told it what would our User class be.

Next configure FOSFacebook, Add at the end of config.yml

fos_facebook:
    file:   %kernel.root_dir%/../vendor/facebook/php-sdk/src/base_facebook.php
    alias:  facebook
    app_id: 
    secret: 
    cookie: true
    permissions: [email,user_birthday,user_location]

Now lets tell FOSFacebook to use our user provider

services:
    my.facebook.user:
        class: Acme\DemoBundle\Security\User\Provider\FacebookProvider
        arguments:
            facebook: "@fos_facebook.api"
            userManager: "@fos_user.user_manager"
            validator: "@validator"
            container: "@service_container"

Still here?! great now for the final part lets edit security.yml.  Now the trick part comes here, Symfony 2.1 introduces the concept of chaining providers, that is that you can stack providers and each will be asked if it knows the user, if not then handles the request to the next one (in the order they are defined) until it is authenticated, if not then authentication is denied.

in_memory:
        memory:
            users:
                user:  { password: userpass, roles: [ 'ROLE_USER' ] }
                admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ]}

    my_fos_facebook_provider:
        id: my.facebook.user

Chaining providers provides a cleaner way to implement this than method used in Symfony 2.0

Setup the firewalls.

Finally the firewalls, after this you should be able to test your app and log in by either method

    firewalls:

        login:
            pattern:  ^/demo/secured/login$
            security: false

        main:
            pattern:    ^/demo/secured/
            form_login:
                check_path: /demo/secured/login_check
                login_path: /demo/secured/login
                default_target_path: /demo/secured/hello/world

            fos_facebook:
                app_url: "http://apps.facebook.com/AppName/"
                server_url: "http://localhost/facebookApp"
                login_path: /login
                check_path: /demo/secured/facebook/login_check
                default_target_path: /demo/secured/hello/world

            logout:
                path:   /demo/secured/logout
                target: /demo/secured/login

!Extra: just in case remember to build the database

$ php app/console doctrine:database:create
$ php app/console generate:doctrine:entities Acme
$ php app/console doctrine:schema:create

Well that’s it, I admit it was more complex that I would like, the user provider really helps to achieve loose coupling, but still I feel that too much configuration was needed (I’m new to Symfony, though).

Hope this tutorial helps you on your development.

!Important: If you are testing on your local machine remember that the VirtualHost URL you set must be the same that you setup on the Facebook App in order for facebook connect to work.

References: If you are curious about the internals of authentication Mathias Noback has an excellent series about the topic, check it out here.  Also checkout Symfony’s docs. User provider and User class taken from FOSFacebookBundle documentation.