tags:

views:

96

answers:

3

I'm working on a Symfony project (my first) where I have to retrieve, from my Widget class, a set of widgets that belong to a Page. Before returning the results, though, I need to verify--against an external service--that the user is authorized to view each widget. If not, of course, I need to remove the widget from the result set.

Using CakePHP or Rails, I'd use callbacks, but I haven't found anything similar for Symfony. I see events, but those seem more relevant to controllers/actions if I'm reading things correctly (which is always up for discussion). My fallback solution is to override the various retrieval methods in the WidgetPeer class, divert them through a custom method that does the authorization and modifies the result set appropriately. That feels like massive overkill, though, since I'd have to override every selection method to ensure that authorization was done without future developers having to think about it.

It looks like behaviors could be useful for this (especially since it's conceivable that I might need to authorize other class instances in the future), but I can't find any decent documentation on them to make a qualified evaluation.

Am I missing something? It seems like there must be a better way, but I haven't found it.

Thanks.

A: 

Here's some documentation on Symfony 1.2 Propel behaviors: http://www.symfony-project.org/cookbook/1_2/en/behaviors.

Why not just have a 'getAllowedWidgets' method on your Page object that does the checks you're looking for? Something like:

public function getAllowedWidgets($criteria = null, PropelPDO $con = null) {
  $widgets = $this->getWidgets($criteria, $con);
  $allowed = array();

  // get access rights from remote service

  foreach($widgets as $widget) {
    // widget allowed?
    $allowed[] = $widget;
  }

  return $allowed;
}

However, if you always want this check to be performed when selecting a Page's Widgets then Propel's behaviours are your best bet.

Cryo
It's the _always_ part that screams for something more than an operation on a single high-level method. I see a discouragingly small amount of documentation on Symfony behaviors and all of it seems to be 1.2-centric. I'm using 1.4.1. Has the behavior mechanism changed since 1.2? Thanks for your help.
Rob Wilkerson
Behaviors actually may not be your solution, do you want to implement this "viewable" check on more objects than just your Page Widgets? If not then a direct override in your model will do just fine, don't worry about creating an entire behavior.The Rails/Cake hooks work exactly the same way as behaviors and method overrides, the only real difference is the clean code separation you get built in with pre* and post* methods. Symfony does, however, allow you to override absolutely everything.Hope that helps.
Cryo
That does help. I like the encapsulation of callbacks, but tried and true OOP overrides will do the job. As I mentioned in my answer above, I'll worry about creating a behavior when I want/need to authorize more that just the `Widget` model. Thanks again.
Rob Wilkerson
A: 

Although, at least in theory, I still think that a behavior is the right approach, I can't find a sufficient level of documentation about their implementation in Symfony 1.4.x to give me a warm and fuzzy that it can be accomplished without a lot of heartburn, if at all. Even looking at Propel's own documentation for behaviors, I see no pre- or post-retrieval hook on which to trigger the action I need to take.

As a result, I took my fallback path. After some source code sifting, though, I realized that it wasn't quite as laborious as I'd first thought. Every retrieval method goes through the BasePeer model's doSelect() method, so I just overrode that one in the customizable Peer model:

static public function doSelect( Criteria $criteria, PropelPDO $con = null ) {
  $all_widgets = parent::doSelect( $criteria, $con );
  $widgets     = array();

  foreach ( $widgets as $i => $widget ) {
    #if( authorized ) {
    #  array_push( $widgets, $widget );
    #}
  }

  return $widgets;
}

I haven't wired up the service call for authorization yet, but this appears to work as expected for modifying result sets. When and if I have to provide authorization for additional model instances, I'll have to revisit behaviors to remain DRY, but it looks like this will suffice nicely until then.

Rob Wilkerson
+1  A: 

First of all, I think behavior-based approach is wrong, since it increases model layer coupling level.

There's sfEventDispatcher::filter() method which allows you to, respectively, filter parameters passed to it.

So, draft code will look like:

<somewhere>/actions.class.php

public function executeBlabla(sfWebRequest $request)
{
  //...skip...
  $widgets = WidgetPeer::getWidgetsYouNeedTo();
  $widgets = $this->getEventDispatcher()->filter(new sfEvent($this, 'widgets.filter'), $widgets));
  //...skip...
}

apps/<appname>/config/<appname>Configuration.class.php

//...skip...
  public function configure()
  {
    $this->registerHandlers();
  }
  public function registerHandlers()
  {
    $this->getEventDispatcher()->connect('widgets.filter', array('WidgetFilter', 'filter'));
  }
//...skip

lib/utility/WidgetFilter.class.php

class WidgetFilter
{
  public static function filter(sfEvent $evt, $value)
  {
    $result = array();
    foreach ($value as $v)
    {
      if (!Something::isWrong($v))
      {
        $result[] = $v;
      }
    }
  }
}

Hope you got an idea.

develop7
I think I get the idea, but I think the model is where I want this coupled, not the controller. No matter why a widget is being requested, I need to ensure that the current user is authorized to see that widget. At the moment, only pages have reason to request widgets, but this is subject to change. If authorization data were held in the database, I'd filter in the `WHERE` clause.
Rob Wilkerson
Ok, call `sfContext::getInstance()->getEventDispatcher()->filter()` from model or anywhere you want.
develop7