Another shameless plug for Penultimate. I am definitely having fun with these. I hope you enjoy reading this as much as I enjoyed drawing it!
I’ve been reading up on OAuth 2.0, and given my current addiction to Draw Something, it comes at no surprise I’ve created the comic strip below. At last, learning is fun again.
Credits:
- Third Party Client – played by the Valet
- Service Provider – played by the Stables
- Information – played by the Horse
Additional Notes:
- All the transactions above must take place over SSL
- The Third Party Client must register a Client ID with the Service Provider in order to use OAuth.
- The Third Party Client must provide a Client Secret along with the Authorization Code in order to retrieve the Access Token.
Every website uses some form of contextualization. In other words, users see information based on their context. A user being tied to a “company” or “organization”, which sandboxes the data they see is one example of this.
![]() |
!= |
|
Adding context to the service container allows you to pass the current user or organization as dependencies to objects requiring a context in order to function. This post aims to show the ease of contextualizing your website with the Symfony2 Service Container, and will demonstrate how to:
- add context to the container
- switch context for admin users
- reference context in twig templates
- implement context security
Add context to the container
As with most things in Symfony2, it all starts with the container. The primary context object is your user. We will add this to the container with a User factory:
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<services>
<!-- Current User Factory -->
<service id="acme.user_factory" class="Acme\Demo\Bundle\Factory\UserFactory">
<argument type="service" id="security.context" />
</service>
<!-- The Current User -->
<service id="context.user" class="Acme\DemoBundle\Entity\User"
factory-service="acme.user_factory"
factory-method="get">
</service>
</services>
Note: By nature, factories must always be declared
public, even if they are never retrieved from the container.
Factory classes allow the creation of a service when retrieving that service requires some additional logic to do so. The cookbook article How to Use A Factory To Create a Service outlines this concept in detail. The UserFactory class in this case will retrieve the logged-in user from the security context.
namespace Acme\DemoBundle\Factory;
use Symfony\Component\Security\Core\SecurityContextInterface;
class UserFactory
{
private $context;
public function __construct(SecurityContextInterface $context)
{
$this->context = $context;
}
public function get()
{
if (null === $token = $this->context->getToken()) {
return null;
}
if (!is_object($user = $token->getUser())) {
return null;
}
return $user;
}
}
This class requests a token from the security context, and verifies the user retrieved from the token is an object. The factory then returns the contextualized user, or null if no such user exists.
Note: Factories need to handle the possibility of null context. Login screens, public landing pages, and console tasks are just a few examples of how an application can function outside of a context.
Any other contextual objects pertaining to the logged in user, such as a company or organization, can be added to the service container in a similar fashion. The example below shows how to add a user’s company to the container.
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<services>
<!-- … -->
<!-- Current Company Factory -->
<service id="acme.company_factory" class="Acme\DemoBundle\Factory\CompanyFactory">
<argument type="service" id="context.user" />
</service>
<!-- The Current Company -->
<service id="context.company" class="Acme\DemoBundle\Entity\Company"
factory-service="acme.company_factory"
factory-method="get">
</service>
</services>
And create the factory class:
namespace Acme\DemoBundle\Factory;
use Acme\DemoBundle\Entity\User;
class CompanyFactory
{
private $user;
public function __construct(User $user = null)
{
$this->user = $user;
}
public function get()
{
$company = null;
if ($this->user) {
$company = $this->user->getCompany();
}
return $company;
}
}
Note: The user argument in the constructor must be optional in the case no user exists in the context.
Now that the contextualized company and user exist in the container, they can be accessed anywhere that has access to the container. The controller is a great place for this. For example, the “Contact List” shown above could be implemented like so:
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ContactController extends Controller
{
public function listAction()
{
$company = $this->get('context.company');
$contacts = $this->get('doctrine.orm.entity_manager')
->getRepository('AcmeDemoBundle:Contact')
->getContactsForCompany($company);
return $this->render('AcmeDemoBundle:Contact:list.html.twig', array(
'user' => $this->get('context.user'),
'contacts' => $contacts,
));
}
}
Switching context for admin users
The CompanyFactory class can be modified in order to allow for context switching. An admin user will want to switch between companies for troubleshooting and administration. This will ward against having ambiguous context (in our case, this would be a user not tied to a company). Certain areas of the site depend on a context, and therefore admin users should switch their context rather than be allowed outside of it.
namespace Acme\DemoBundle\Factory;
use Acme\DemoBundle\Entity\User;
use Symfony\Component\HttpFoundation\Session;
use Doctrine\Common\Persistence\ObjectManager;
class CompanyFactory
{
private $user;
private $session;
private $entity_manager;
public function __construct(User $user = null, Session $session, ObjectManager $entityManager)
{
$this->user = $user;
$this->session = $session;
$this->entity_manager = $entityManager;
}
public function get()
{
$company = null;
if ($companyId = $this->session->get('context.company_id')) {
$company = $this->entity_manager
->getRepository('AcmeDemoBundle:Company')
->find($companyId);
}
if (!$company && $this->user) {
$company = $this->user->getCompany();
}
return $company;
}
}
The session storage service and the entity manager service are now passed to the factory. This enables the factory to check the http session for the existence of a company identifier and ask the entity manager for this company. If this is null, the user’s default company is used instead.
Because additional dependencies have been added to the factory, the service definition needs to be updated:
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<services>
<!-- … -->
<!-- Current Company Factory -->
<service id="acme.company_factory" class="Acme\DemoBundle\Factory\CompanyFactory">
<argument type="service" id="context.user" />
<argument type="service" id="session" />
<argument type="service" id="doctrine.orm.default_entity_manager" />
</service>
<!-- The Current Company -->
<service id="context.company" class="Acme\DemoBundle\Entity\Company"
factory-service="acme.company_factory"
factory-method="get">
</service>
</services>
Use the Service as a Dependency
The context classes can now be used like the dependency it is. For example, to filter objects in a list by context using the SonataAdminBundle, the current context can be passed to the admin service through the container
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<services>
<!-- … -->
<service id="acme.admin.contact" class="Acme\DemoBundle\Admin\ContactAdmin">
<tag name="sonata.admin" manager_type="orm" group="admin" label="contact"/>
<argument />
<argument>Acme\DemoBundle\Entity\Contact</argument>
<argument>SonataAdminBundle:CRUD</argument>
<property name="company" type="service" id="context.company" />
</service>
</services>
The context company is now available in the admin class, and filtering the admin list is as easy as doing the following:
namespace Acme\DemoBundle\Admin;
class CompanyAdmin extends Sonata\AdminBundle\Admin\Admin
{
public $company;
//...
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
$query->getQueryBuilder()
->andWhere('o.company = :company')
->setParameter('company', $this->company)
;
return $query;
}
}
Reference context in twig templates
It is very handy to have context objects available inside the twig templates. To do this, set the services as globals in the twig configuration.
# app/config/config.yml
twig:
globals:
currentUser: @context.user
currentCompany: @context.company
This enables you to do very handy things like display the context in a twig block (like in the “Contact List” example above).
# app/Resources/views/user_block.html.twig
{% block user_block %}
<h3>logged in as {{ currentUser.username }} ({{ currentCompany.name }})</h3>
{% endblock %}
Implement context security
Any website implementing context will need to protect items existing outside the current context. This step in the process is easy to dismiss when deadlines and other priorities jockey for attention, but is a must have for any contextual application. Your client doesn’t want curious users engineering URLs and reeking havoc on your other users.
The first step will be to create a Voter class. Voter classes are used by the security context to manage access. They have three possible outcomes, Grant, Deny, and Abstain.
namespace Acme\DemoBundle\Security\Authorization\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ContextVoter implements VoterInterface, ContainerAwareInterface
{
public $container;
public function supportsAttribute($attribute)
{
return $attribute === 'CONTEXT';
}
public function supportsClass($class)
{
return true;
}
public function vote(TokenInterface $token, $object, array $attributes)
{
if ($this->supportsClass($object) && $company = $this->container->get('context.company')) {
foreach ($attributes as $attribute) {
if ($this->supportsAttribute($attribute)) {
if ($company == $object->getCompany()) {
return VoterInterface::ACCESS_GRANTED;
}
return VoterInterface::ACCESS_DENIED;
}
}
}
return VoterInterface::ACCESS_ABSTAIN;
}
}
Note: The
service_containerservice is passed in rather than thecontext.companyservice to avoid a ServiceCircularReferenceException. In this case,context.userdepends on the security context, which depends on voters. Requesting the company at runtime solves this issue.
The voter loops through the requested attributes and determines whether or not it can grant access to this object. If the attribute being requested is CONTEXT, the voter grants or denies access accordingly.
This can now be configured in the service container:
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<services>
<!-- … -->
<!-- Context Voter -->
<service id="context.voter" class="Acme\DemoBundle\Security\Authorization\Voter\ContextVoter" public="false">
<tag name="security.voter" />
<property name="container" type="service" id="service_container" />
</service>
</services>
Pass the CONTEXT attribute and appropriate object to the isGranted function anywhere the security context is present:
public function showAction($id)
{
//...
if (!$this->get('security.context')->isGranted(array("CONTEXT"), $entity)) {
throw new Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException();
}
//...
}
This may seem overkill. After all, the call to isGranted could simply be replaced by explicitly checking the company, no voters, security context, or dependency injection required! Working with the security context now will save a lot of headache in the future. Context may be more complicated than a simple “company” class. Consider GooglePlus circles, or Facebook’s privacy policy, as complex examples of context. Using a voter will allow this logic to stay outside the controller.
Postmortem
Context is complicated, and almost every SaaS application requires it. The service container in Symfony2 helps mitigate these complexities, and allows the current context to behave as a dependency. Take the extra time to include context in your container, and your application will be much healthier because of it.
Meet Hadori. She’s beautiful, magnificient, and everything you’ve ever dreamed her to be. Go head, don’t be shy… click on her!. You can also browse the fully-functional demo, if you’re into that kind of thing.
| List View | |
|---|---|
![]() |
|
| Filters | Editing |
|
![]() |
| Detail View (show) | Exporting (yeah!) |
![]() |
![]() |
Why Switch?
- Export models as CSV
- Generated code that doesn’t strangle you
- Full test coverage
- Finally… a show action!
- Support for sortable models
- Gorgeous Redmine-style filtering
- Optional internationalization support
- Integration with
security.ymlsettings - Semantic markup, semantic CSS, and a beautiful theme!
- Damn good documentation
The primary purpose of Hadori is to provide you with meaningful, generated code. The purpose of an admin generator is to get you started with basic functionality that every administrator needs. However, your app’s requirements will eventually outgrow the scope of any configurable generator. When this happens, Hadori ensures you hit the ground running.
The generated code in Hadori isn’t good. It’s gorgeous. Beautiful. Hadori writes code better than your mom, guaranteed. Check it out.
Below is a comparison between symfony’s built-in admin generator and Hadori. This has been done using the out-of-the-box configuration for the sfGuardUser model. You can quickly see how much more lightweight hadori is, but more importantly you can see how much easier it is to understand the code itself.
| Hadori | Symfony’s Admin Gen | compare | |
|---|---|---|---|
| list header partial | 25 lines | 161 lines | compare |
| form rendering | 1 partial | 4 partials | compare |
| generated configuration class | none! | 293 files | compare |
| filesystem | 14 files | 29 files | compare |
| process form action | 15 lines | 42 lines | compare |
| batch action | 13 lines | 44 lines | compare |
| generated helper class | none! | 18 files | compare |
So what next?
- Check out the fully-functional demo
- Read the awesome documentation
- View a sample generator.yml from the demo
- Poke around the demo’s generated code
- Once you’re convinced Hadori is the plugin for you, follow the Installation and Setup instructions on github
- As always, don’t hesitate to contact me with any questions!
note: I have every intention of using as much of these concepts as possible in the Sonata AdminBundle for Symfony2. Stay tuned!
A while ago, I wrote a post entitled “Faux Form Serialization“. It was an overly-complex solution to a very easy problem. Today, I am writing about a much better implementation for solving the same problem.
What is this concept? You’ve heard of MongoDB right? CouchDB? BeullerDB…? Of course you have. You’ve been tired of writing migrations to add and remove columns that are constantly changing, and at some point have had the thought
“By golly, I should just slap an options field on this honkey!”
You’re exactly right, and this is what you should do.
Doctrine already has a lovely array field type. Similarly, it serializes an array into a longtext database column and unserializes upon hydration. Well that sounds useful!
Exposing these fields can be tricky though. We have this lovely form framework with all its pretty widgets and validators, and the ability to embed them! Yet, since this isn’t a true related object, we can’t do this out of the box… or can we?!
Example Time
Your sfGuardUser object has a laundry list of silly auxiliary fields, including items such as “how often do you zumba?” and “what animal produces your favorite milk?”. You refuse to write migrations for these things. Instead, you’ll save them all to a single field of type array called extra_profile_fields. Good for you! Create a form:
// lib/form/sfGuardUserExtraProfileFieldsForm.class.php
class sfGuardUserExtraProfileFieldsForm extends BaseForm
{
public function configure()
{
$this->setWidgets(array(
'zumba_frequency' => new sfWidgetFormChoice(array('choices' => array('Not at all', 'Sometimes', 'Every Single Day', 'I NEVER STOP'))),
'favorite_animal_milk' => new sfWidgetFormInput(),
));
$this->setValidators(array(
'zumba_frequency' => new sfValidatorChoice(...),
'favorite_animal_milk' => new sfValidatorString(...),
));
}
}
Now, use my very special sfWidgetFormEmbeddedForm and sfValidatorEmbeddedForm
// lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends PluginsfGuardUserForm
{
public function configure()
{
....
$form = new sfGuardUserExtraProfileFieldsForm();
$this->widgetSchema['extra_profile_fields'] = new sfWidgetFormEmbeddedForm(array('form' => $form));
$this->validatorSchema['extra_profile_fields'] = new sfValidatorEmbeddedForm(array('form' => $form));
}
Wow, is it that easy? YES IT IS. The form will render expectedly, with validators and all. However, make sure you pass the same form object to both the widget and the validator! If you, for instance, pass a new sfGuardUserExtraProfileFieldsForm instance to each, you’ll have two separate forms, and errors in your form will not be rendered.
Extra Fun: Using Archivers
Let’s say you wanted the options to save as YAML, JSON, or a PHP-serialized string. As long as you use the “array” column type in doctrine, none of this is necessary. But if you need this flexibility, I’ve got your back.
The ArchiverInterface specifies a class with three methods: sleep, wake, and isAsleep. Essentially, it will convert from a certain array to a format and back again. Here are some sample archivers for your pleasure alone. Pass the class name as the “archiver” option to widget and validator, and you’re all set!
Hopefully you guys will enjoy this and use this as much as I have. And please don’t hesitate to submit comments, suggestions, and questions.
- All the classes in this blog post can be found on github.








RSS Feed