Skip to content

[FOSTwitterBundle]: Simple way to tweet

Introduction

In this post I will show you the simplest way to post a tweet from your Symfony2 application. I assume that you have followed FOSTwitterBundle the install steps, and already registered your app in http://dev.twitter.com, set read & write permissions to post to your twitter account.

Find and write down you consumer keys and access tokens keys in your http://dev.twitter.com account

Twitter dev keys

Configure you config.yml


#app/config/config.yml
fos_twitter:
    file: %kernel.root_dir%/../vendor/twitteroauth/twitteroauth/twitteroauth.php
    consumer_key: xxxxxx
    consumer_secret: xxxxxx

From your controller get the fos_twitter.api service, and call the setOAuthToken to set the access tokens keys (fixed values).


 /**
     * @Route("/tweet/{status}", name="_demo_tweet")
     * @Template()
     */
    public function tweetAction($status)
    {
        $tweet = $this->get('fos_twitter.api');
        $tweet->setOAuthToken(access_token, access_token_secret);

        $tweet->post('statuses/update', array('status' => $status));

        return $this->render('AcmeDemoBundle:Demo:tweet.html.twig',
                             array('status' => $status));
    }

For full list of methods, take a look to Twitter REST API

Hope it helps.

Symfony2 – Dynamic forms, an event-driven approach

Introduction

In the previous article, I explained a rather complicated way to have select boxes populated dynamically, in this article I will explain a new cleaner approach using events in forms.

This solution came from the article “Symfony2 Forms – The AJAX City/State Select” which I found in this issue in the Symfony2 mailing list.

As in the previous article we are in the need of populating a select box with data depending on a previous action, normally after selecting an option in other select box, check box, etc.

For this article I will use, equivalent to the well known city/states example a locality/province example. Here is the model.

The Model

province one-to-many localities

Province/Locality one-to-many relation

As you see one province has a one-to-many relation to localities. The concept is simple, we have two select boxes, one for province and one for locality. When the province changes, we would like to populate the locality select box with localities form the selected province.

class Province
{
	/**
	 * @ORM\Id
	 * @ORM\Column(type="integer")
	 * @ORM\GeneratedValue(strategy="NONE")
	 */
	private $id;

	/**
	 * @ORM\Column(type="string", length="255", nullable=false)
	 */
	private $name;

   /**
     * Inverse Side
     * @ORM\OneToMany(targetEntity="Locality", mappedBy="province")
     */
    private $localities;
}

class Locality
{
    /**
    * @ORM\Id
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="NONE")
    */
    protected $id;

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

    /**
     *
     * owning Side
     * @ORM\ManyToOne(targetEntity="Province", inversedBy="localities")
     * @ORM\JoinColumn(name="province_id", referencedColumnName="id")
     */
    protected $province;

For better re-usability I use the Location class/entity that can be easily copied.

class Location
{
    /**
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\ManyToOne(targetEntity="Province", inversedBy="locations")
     * @ORM\JoinColumn(name="province_id", referencedColumnName="id")
     */
    protected $province;

    /**
     *
     * @ORM\Column(name="address", type="string", length=255, nullable=true)
     */
    protected $address;

     /**
     * @var string $zipcode
     *
     * @ORM\Column(name="zipcode", type="string", length=20, nullable=true)
     */
    protected $zipCode;

Location Province Locality relations

Final Model

A great improvement on previous article is that the relation between Location and Locality is not needed anymore, that simplifies things.

Events

Once entities/classes have been created, it’s time to create the forms and attach the events listeners to it. This time there won’t be the need of creating any custom type or data transformers, as events will take care of loading proper localities when the data is assigned to the forms (FormEvents::PRE_SET_DATA), and again on the way back, binding (FormEvents::PRE_BIND), if province has change and an AJAX request has been done to populate localities.

class LocationType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $factory = $builder->getFormFactory();

        $builder->add('province','entity',array(
                       'class' => 'Acme\MyBundle\Entity\Province',
                       'property' => 'name'));

        $refreshLocality = function ($form, $province) use ($factory) {
            $form->add($factory->createNamed('entity','locality',null, array(
                'class'         => 'Acme\MyBundle\Entity\Locality',
                'property'      => 'name',
                'label'         => 'Locality',
                'query_builder' => function (EntityRepository $repository) use ($province) {
                                       $qb = $repository->createQueryBuilder('locality')
                                                        ->innerJoin('locality.province', 'province');

                                       if($province instanceof Province) {
                                           $qb = $qb->where('locality.province = :province')
                                                    ->setParameter('province', $province);
                                       } elseif(is_numeric($province)) {
                                           $qb = $qb->where('province.id = :province_id')
                                                    ->setParameter('province_id', $province);
                                       } else {
                                           $qb = $qb->where('province.id = 1');
                                       }

                                       return $qb;
                                   }
                 )));
        };

        $builder->add('address','text',array(
                        'required' => false));

        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (DataEvent $event) use ($refreshLocality) {
            $form = $event->getForm();
            $data = $event->getData();

            if($data == null)
               $refreshLocality($form, null); //As of beta2, when a form is created setData(null) is called first

            if($data instanceof Location) {
                $refreshLocality($form, $data->getLocality()->getProvince());
                }
        });

        $builder->addEventListener(FormEvents::PRE_BIND, function (DataEvent $event) use ($refreshLocality) {
            $form = $event->getForm();
            $data = $event->getData();

            if(array_key_exists('province', $data)) {
                $refreshLocality($form, $data['province']);
            }
        });
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'Acme\MyBundle\Entity\Location'
        );
    }

    public function getName()
    {
        return 'location';
    }
}

refreshLocality is a closure to be used as a callback in events to create and populate the locality field. Before the data is set we populate or locality field with localities from the province loaded, null as creation, and before we bind our data we re-create our locality field with cities from selected province.

AJAX Request

To bring live to forms, every time the province changes, locality select box must be populated with new localities, so a bit of client code is needed.

    $('#myform_location_province').change( function() {
        $.ajax({
            type: "GET",
            data: "data=" + $(this).val(),
            url:"{{ path('_localityByProvinceId') }}",
            success: function(msg){
                if (msg != ''){
                   $('#myform_location_locality').html(msg).show();
                   }
                   else
                   {
                       $('#myform_location_locality').html('<em>No item result</em>');
                   }
               }
           });
      });

Requested localities must be built as an HTML string and sent as response, place this function inside a controller.


     /**
     * @Route("/listByProvince", name="_localityByProvinceId")
     */
    public function getByProvinceId()
    {
        $this->em = $this->get('doctrine')->getEntityManager();
        $this->repository = $this->em->getRepository('RunnerMainBundle:Locality');

        $provinceId = $this->get('request')->query->get('data');

        $localities = $this->repository->findByProvince($provinceId);

        $html = '';
        foreach($localities as $locality)
        {
            $html = $html . sprintf("<option value=\"%d\">%s</option>",$locality->getId(), $locality->getName());
        }

        return new Response($html);
    }

Final Notes

Thanks to Roger Webb for your post which I based this one.

Dynamically populate select boxes with Symfony2 & jQuery

Introduction

Having multiple select boxes and populate them upon previous selection is quite familiar and useful (country/cities example)

In this article I will explain a way (not the best nor the unique) to have it working with symfony2 and a little but inestimable  help of jQuery. We will be facing some concepts  like DataTransformers, FormTypes, Depedency Injection and some more, so be prepared.

We’ll go through a simple example of creating/editing providers. When adding new providers, location must be set. Location is composed of a community, province, city and street. Province and community are select boxes, for sake of simplicity I haven’t used a select box for cities. Province is related to community, that means, once we select a community, related provinces will be loaded from database and into province select box.

Community/province case is similar to well-known country/cities case. This is a real example about administrative division in Spain.

You can find the source code for this example in my github account. An on-line example to play with is available here.

community_province_model

community province model

Create the entities

The first step is to create the needed entities into Entiy folder.

class Community
{
/**
* @var integer $id
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

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

/**
* @ORM\OneToMany(targetEntity="Community", mappedBy="community")
*/
private $provinces;

/**
* @ORM\OneToMany(targetEntity="Location", mappedBy="province")
*/
private $locations;

 

class Province
{
/**
* @var integer $id
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

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

/**
* @ORM\ManyToOne(targetEntity="Community", inversedBy="provinces")
* @ORM\JoinColumn(name="community_id", referencedColumnName="id")
*/

private $community;

/**
* @ORM\OneToMany(targetEntity="Location", mappedBy="province")
*/
private $locations;

As you can see there is a new entity named Location, which will be handy at the time of building the forms and separate things. This approach has a drawback increasing the number of relations and breaking some canonical forms in database design. Hopefully someone get this right.
The new model, adding provider entity, will look like this

model complete

model complete


class Location
{
/**
* @var integer $id
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @ORM\ManyToOne(targetEntity="Community", inversedBy="locations")
* @ORM\JoinColumn(name="community_id", referencedColumnName="id")
*/
private $community;

/**
* @ORM\ManyToOne(targetEntity="Province", inversedBy="locations")
* @ORM\JoinColumn(name="province_id", referencedColumnName="id")
*/
private $province;

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

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

 


class Provider
 {
 /**
 * @var integer
 *
 * @ORM\Column(type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
 protected $id;

/**
 * @var string
 *
 * @ORM\Column(type="string")
 * @Assert\NotBlank(groups="Provider")
 */
 protected $name;

/**
 * @var string
 *
 * @ORM\Column(type="string")
 * @Assert\NotBlank(groups="Provider")
 */
 protected $phone;

/**
 * @ORM\OneToOne(targetEntity="Location");
 * @ORM\JoinColumn(name="location_id", referencedColumnName="id")
 */
 protected $location;

For the creation of getters and setters we  can use the console command.

app/console doctrine:generate:entities

Loading with fixtures

I’ve created some fixtures in yaml with communities, provinces,locations and providers to start working.This is out of the scope of this article so take a look at the code if you are interested.

You can try to load the fixtures once database and schema is created with the console command.

app/console doctrine:load:fixtures

Building forms

To allow create and edit providers we build the following form

class ProviderType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');
        $builder->add('phone');
        $builder->add('location',new LocationType());
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'Acme\StoreBundle\Entity\Provider'
        );
    }

    public function getName()
    {
        return 'provider';
    }

}

ProviderType has a LocationType embedded, this cool feature of symfony2 saves us a lot of time and simplifies things. LocationType looks like this:

class LocationType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
       $builder->add('community','entity',array(
           'class' => 'Acme\StoreBundle\Entity\Community',
           'property' => 'name'));
       $builder->add('province','null_entity',array(
           'class' => 'Acme\StoreBundle\Entity\Province',
           'property' => 'name'));
       $builder->add('city');
       $builder->add('street');
    }

    public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\StoreBundle\Entity\Location'
);
}

    public function getName()
    {
        return 'location';
    }
}

Note how community and province are build. The first one is an entity type which retrieves the data from the database and shows up as a select box. Second one is created as null_entity, a custom type that simply does shows and empty select box and register some data transformers to convert the data coming from data submitted in the request string (id of the entity) into it’s php object class, in this case Province class.

Creating and Registering the null_entity type.

The need of this type is that by default EntityType populates the select box with all the data in the database (Province table in this case). Imagine that province table is holding 8000 record (think about countries around the world) . This is completely unnecessary as province select box will be  populated using jQuery with the needed data once a community is selected. To override this behaviour I have created this null_entity which does not load any data by default, but register the DataTransformers.

I have followed Symfony2 source code to place this file, so it allows the implementation of other ORM or ODM. It is interesting to have this FormType along with its DataTransformer in their own bundle as this is likely to be reusable.

  •   StoreBundle
    • Bridge
      • Doctrine
        • Form
          • DataTransformer
            • TextToIdTransformer.php
          • Type
            • NullEntityType.php
class NullEntityType extends AbstractType
{
    public function __construct(RegistryInterface $registry)
    {
        $this->registry = $registry;
    }

    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->prependClientTransformer(new TextToIdTransformer(
            $this->registry->getEntityManager($options['em']),
            $options['class'],
            $options['property']
        ));
    }

    public function getDefaultOptions(array $options)
    {
        $defaultOptions = array(
            'em'                => null,
            'class'             => null,
            'property'          => null,
            'hidden'            => false,
        );

        $options = array_replace($defaultOptions, $options);

        return $options;
    }

    public function getParent(array $options)
    {
         if ($options['hidden']) {
            return 'hidden';
         }
         return 'choice';
    }

    public function getName()
    {
        return 'null_entity';
    }
}

The most interesting thing in this code is the registering of the custom DataTransformer ‘TextToIdTransformer’ which I’ll explain in the next section, but first we need to register this type into de Dependency Injection Container, for better and reusable code .

Create the needed service into Resources/config/services.xml file

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
    <service id="form.type.null_entity">
        <tag name="form.type" alias="null_entity"/>
            <argument type="service" id="doctrine"/>
    </service>
</services>
</container

We create a new service called form.type.null_entity, tagged as ‘form.type‘ to be recognised within the build form process, and inject doctrine service needed inside the class NullEntityType by the DataTransformer.

Create the following class into DependencyInjection folder to load the created service

class AcmeStoreExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
         $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
         $loader->load('services.xml');
    }

    public function getAlias()
    {
        return 'acme_store';
    }
}

This will load the service specified into the xml file.

Creating your own DataTransformer

A DataTransformer is at the time of this writing still undocumented (Symfony 2 API), basically what it does is to transform a value from its original representation to a transformed representation.

What it is for? It’s use, mainly is to serve as a channel of communication between how data must be shown in a browser and how data is stored/managed it the data class. To serve as an example, let’s think about dates, dates are shown in a client browser as select boxes or as  simple as textbox, let’s get this last representation for our example. Once the form is submitted input data must be processed and stored as a DateTime PHP Object, so some transformation is needed back and forth.

DataTransformerFlowData

DateTimeToLocalizedStringTransformer Data Flow Diagram

Why don’t use EntityType?

In the previous section we created an registered a new FormType called NullEntityType. As explained before EntityType which register EntityToIdTransformer as default DataTransformer retrieves data from the dataClass passed by parameter. This behaviour does not fit our needs as selectbox will be populated using jQuery every time the specified event triggers, that’s it when community select box changes.

Even more EntityToIdTransformer expects an EntityChoiceList as argument to get data from. So finally I ended up creating my own DataTransformer and naming it TextToIdTransformer which transform the Id returned by select box to its convenient object to be persisted.


class TextToIdTransformer implements DataTransformerInterface
{

    protected $em;
    protected $class;
    protected $propertyPath;

    public function __construct(EntityManager $em, $class, $property = null)
    {
         $this->em = $em;
         $this->class = $class;

         // The property option defines, which property (path) is used for
         // displaying entities as strings
        if ($property) {
             $this->propertyPath = new PropertyPath($property);
        }
    }

    public function transform($entity)
    {
        if (null === $entity || '' === $entity) {
            return 'null';
        }

        if (!is_object($entity)) {
            throw new UnexpectedTypeException($entity, 'object');
        }

       if ($this->propertyPath) {
       // If the property option was given, use it
            $value = $this->propertyPath->getValue($entity);
       } else {
           // Otherwise expect a __toString() method in the entity
           $value = (string)$entity;
       }
       return $value;
    }

    public function reverseTransform($key)
    {
          if ('' === $key || null === $key) {
          return null;
          }

         if (!is_string($key))
         {
             return null;
         }

         if (!is_numeric($key))
         {
             throw new UnexpectedTypeException($key, 'numeric');
         }

         $entity = $this->em->getRepository($this->class)->findOneById($key);

         if ($entity === null) {
             throw new TransformationFailedException(sprintf('The entity with key "%s" could not be found', $key));
         }

        return $entity;
    }
}

Where jQuery gets into action

A bit of client code is needed to raise an event that fetches convenient data from database

<script type="text/javascript">
$(document).ready(function(){

    $("#provider_location_community").change( function() {
        $("#loader").show();
        $.ajax({
            type: "GET",
            data: "data=" + $(this).val(),
            url:"{{ path('_provinceByCommunity') }}",
            success: function(msg){
                if (msg != ''){
                    $("#provider_location_province").html(msg).show();
                    $('#provider_location_province option[value=' +
                        {{ provider.location.province.id is defined ? provider.location.province.id : '' }}
                    +']').attr("selected","selected");
                    $("#loader").hide();
                }
                else
                {
                    $("#provider_location_province").html('<em>No item result</em>');
                    $("#loader").hide();
                }
           }
       });
   });

   $("#provider_location_community").trigger('change');
});
</script>

Every time a new community is selected a new request to its convenient controller is done, in this case the controller build a HTML string and send it back as a response to be shown in the select box.

/**
* @Route("/provinceByCommunity", name="_provinceByCommunity")
*/
public function listProvinceByCommunityId()
{
    $this->em = $this->get('doctrine')->getEntityManager();
    $this->repository = $this->em->getRepository('AcmeStoreBundle:Province');

    $communityId = $this->get('request')->query->get('data');

    $provinces = $this->repository->findByCommunity($communityId);

    if (empty($provinces)) {
        return new Response('<option>No provinces found for that community</option>');
    }

    $html = '';
    foreach($provinces as $province)
    {
        $html = $html . sprintf("<option value=\"%d\">%s</option>",$province->getId(), $province->getName());
    }

    return new Response($html);
}

Final Notes

We have reached the end of a long and quite complicated journey. As stated in the beginning of the post this is just one solution to this problem, and of course there are many of them,  for sure much better and convenient, to find them and show them us is your task.

Feedback is appreciated.

Hope it helps.

Links

Enhancing your form, adding jQuery UI datepicker with symfony2

By default DateType shows three select boxes, which is good for prototyping. Fortunately DateType comes full-equipped with options to customise its rendering and the date format it accepts. In this post I will show you how to use a datepicker to enhance your forms.

When building the form pass the widget option set to single_text to render it as a simple text box, for full options description see the API documentation.


class ContactType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('email', 'email');
        $builder->add('message', 'textarea');
        $builder->add('birthday','date',array(
             'widget' => 'single_text'
        ));

   }

    public function getName()
    {
        return 'contact';
    }
}

In your template, that can be the base template, load the needed jQuery and jQuery-UI libraries.
Use your favorite UI theme or customise it with theme roller
Finally add the script code to attach the datepicker and the textbox. Note that the name of the id will be the concatenation separated by an underscore ‘_’ of the form name and the type name, in this case contact_birthday.

<head>
    <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"></script>
    <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/ui-lightness/jquery-ui.css" type="text/css" media="all"/>
</head>

<script>
$(function() {
    $( "#contact_birthday" ).datepicker();
});
</script>