zendframework/zend-db

Should I refactor TableGatewayAbstractFactory to support the StrategyEnabledInterface?

Closed this issue · 5 comments

I have a situation where I want to pre-build the select query based on the logged in user (or potentially other criteria) and I’m thinking that the strategy pattern would be the best way to do that, specifically by refactoring TableGatewayAbstractFactory to implement the StrategyEnabledInterface.

Here's a sample use case; I have a fetchAll method which returns all documents in my application, and I want to be able to call just that one method, but transparently take in to account the logged in user, so that they only see the documents which they should.

Say I have a logged in user, and they're an admin user. I want that user to be able to see all records, with no filtering, as they should be able to edit everything. So the select object would be left as is, with nothing pre-added to it.

Later, a normal user logs in. In this case I want to ensure that the user sees only the records which they're responsible for, not ones belonging to any other user. So I'd want to add a join and a where (or other criteria), resulting in pre-filtering the results.

Given that the criteria can change at runtime, if they're applied at all, I'm thinking that a Strategy Pattern is the right approach, as it can keep the logic separate, testable, and not break SRP, as well as be applied if and when needed.

Am I missing existing functionality, or looking at this from the wrong perspective?

On thinking about this further, and after doing some research, I see one of two approaches being the right way forward. Te first one is instead to create a new Feature class and implement the preSelect method. Alternatively, I could use the EventFeature and do attach to a pre select event.

@settermjd You don't give up!

@gianarb I should continue?

@settermjd

Am I missing existing functionality, or looking at this from the wrong perspective?

Maybe the EventFeature?! Short example:

class FoobarTableGatewayListener extends AbstractListenerAggregate
{
    public function attach(EventManagerInterface $events)
    {
        $this->listeners[] = $events->attach(
            EventFeature::EVENT_PRE_SELECT,
            [
                $this,
                'preSelect',
            ]
        );
    }

    public function preSelect(TableGatewayEvent $event)
    {
        if ($this->identity  && 'foobar' === $this->identity->getRole()) {
            /** @var \Zend\Db\Sql\Select $select */
            $select = $event->getParam('select');

            // Add user ID to query
            $select->where(['user_id' => $this->identity->getId()]);
        }
    }
}

And in your factory for the Table Gateway class:

$eventFeature = new Zend\Db\TableGateway\Feature\EventFeature();
$eventManager = $eventFeature->getEventManager();

/** @var \Zend\EventManager\ListenerAggregateInterface $listener */
$listener = $container->get('FoobarTableGatewayListener');
$listener->attach($eventManager);

$featureSet = new Zend\Db\TableGateway\Feature\FeatureSet();
$featureSet->addFeature($eventFeature);

$tableGateway = new TableGateway('foobar', $adapter, $featureSet);