On Wed, Nov 25, 2009 at 9:52 AM, Adam Jensen <jazzsli
...@gmail.com> wrote:
> Thanks again for your help with this; I went through a couple of bug
> reports to find the answer, but I think I've got it working pretty smoothly
> now.
> For anyone who's interested, I've blogged the complete solution at
> http://jazzslider.org/2009/11/25/using-zend-acl-with-doctrine-record-...
> (As a sidenote: this blog is brand-new, and I'm not totally satisfied with
> its cross-browser support …so I apologize if it doesn't look quite right
> yet…but the content should all be there :)
> Thanks again!
> Adam
> On Tue, Nov 17, 2009 at 12:00 PM, Jake Smith <jake.smit...@gmail.com>wrote:
>> Adam,
>> I would submit it as a bug. It is either a bug or there is an
>> undocumented way to handle such situation.
>> http://trac.doctrine-project.org/wiki/ReportBug
>> Jake Smith
>> [t] @jakefolio
>> [w] www.jakefolio.com
>> On Tue, Nov 17, 2009 at 6:28 AM, Adam Jensen <jazzsli...@gmail.com>wrote:
>>> Hello!
>>> OK, I did some experimentation on this yesterday evening, and I think I
>>> have a clearer sense of at least part of the problem.
>>> First thing I needed to find out is which listeners were actually firing
>>> when, so I wrote two very simple listeners (an event listener and a record
>>> listener), each implementations of Doctrine_Overloadable (see last part of Creating
>>> a New Listener<http://www.doctrine-project.org/documentation/manual/1_1/en/event-lis...>);
>>> they looked like this:
>>> [code]
>>> class My_Record_Listener_Tester extends Doctrine_Overloadable
>>> {
>>> public function __call($methodName, $args)
>>> {
>>> echo "Doctrine_Record_Listener::$methodName" . PHP_EOL;
>>> }
>>> }
>>> // and then the same thing for Doctrine_EventListener
>>> [/code]
>>> I then wrote a simple CLI script to bootstrap my Doctrine manager and
>>> connection, attached these listeners as appropriate
>>> (Doctrine_Manager::addListener and Doctrine_Manager::addRecordListener), and
>>> ran the following test code:
>>> [code]
>>> echo "Running Doctrine_Connection::getTable('Post')..." . PHP_EOL;
>>> $table = $conn->getTable('Post');
>>> echo "...done." . PHP_EOL;
>>> echo "Running Doctrine_Table::findAll()..." . PHP_EOL;
>>> $records = $table->findAll();
>>> echo "...done." . PHP_EOL;
>>> [/code]
>>> The first time I tried this, this is all that came out:
>>> [output]
>>> Running Doctrine_Connection::getTable('Post')...
>>> ...done.
>>> Running Doctrine_Table::findAll()...
>>> EventListener::preConnect
>>> EventListener::postConnect
>>> EventListener::preQuery
>>> EventListener::postQuery
>>> ...done.
>>> [/output]
>>> Not a whole lot there to hook into; *Connect() won't help me, and the
>>> *Query() methods aren't really even documented. But I was wondering at this
>>> point, why in the world were none of the hydration hooks running? After
>>> all, I was getting back fully hydrated Post objects.
>>> I took a closer look at my Post model at that point and realized that
>>> it's utilizing several built-in Doctrine templates ("actAs" stuff:
>>> timestampable, sluggable, etc.); since templates make heavy use of event and
>>> record listeners to do their work, I thought I'd test it without them
>>> attached, just to see if there were a conflict. Sure enough, when I ran the
>>> script again:
>>> [output]
>>> Running Doctrine_Connection::getTable('Post')...
>>> ...done.
>>> Running Doctrine_Table::findAll()...
>>> Record_Listener::getOption
>>> Record_Listener::preDqlSelect
>>> EventListener::preConnect
>>> EventListener::postConnect
>>> EventListener::preQuery
>>> EventListener::postQuery
>>> Record_Listener::getOption
>>> Record_Listener::preHydrate
>>> Record_Listener::getOption
>>> Record_Listener::postHydrate
>>> Record_Listener::getOption
>>> Record_Listener::preHydrate
>>> Record_Listener::getOption
>>> Record_Listener::postHydrate
>>> ...done.
>>> [/output]
>>> Suddenly there are a whole slew of Record_Listener events firing that
>>> weren't firing when the actAs templates were still attached. That hydration
>>> sequence (getOption, preHydrate, getOption, postHydrate) fires once for
>>> every record returned…and there's also a getOption, preDqlSelect sequence
>>> that fires before the *Query() events.
>>> Unfortunately, those actAs templates are pretty important, and so far I
>>> haven't found a way to get all those extra hooks to fire if _even one_
>>> template is still attached to the model.
>>> Do you know if this is a documented behavior? If so, is there a
>>> workaround?
>>> I'm going to cross-post this in the Doctrine list too, since I think it
>>> may be useful information …either that, or I'm missing something obvious :)
>>> Thanks!
>>> Adam
>>> On Mon, Nov 16, 2009 at 3:12 PM, Adam Jensen <jazzsli...@gmail.com>wrote:
>>>> Jake,
>>>> Haven't tried that one yet, no…was a bit confused about the difference
>>>> between postExec, postExecute, and postStmtExecute as described in the docs
>>>> at
>>>> http://www.doctrine-project.org/documentation/manual/1_1/en/event-lis...with such similar names, it's tough to know which one to try :)
>>>> I traced through some of the source, though, and it looks like the
>>>> Doctrine_Connection::exec() method handles queries that change things
>>>> (inserts, updates, deletes), while Doctrine_Connection::execute() handles
>>>> queries that return things (selects). So I'm guessing the hooks should work
>>>> similarly, except that if you include prepared statement parameters, the
>>>> *StmtExecute() hooks are required instead (if I'm reading the footnote in
>>>> the manual correctly). Does that all sound right?
>>>> If I get a chance tonight, I'll try a few more of these methods
>>>> out…maybe in a testing environment where I can isolate exactly what's
>>>> happening when…figuring out how all this works in the context of a full
>>>> application is turning out to be a bit messy.
>>>> Meanwhile, I appreciate the help; pretty excited to get this working!
>>>> Thanks,
>>>> Adam
>>>> On Mon, Nov 16, 2009 at 1:57 PM, Jake Smith <jake.smit...@gmail.com>wrote:
>>>>> Adam,
>>>>> Have you tried postExec()?
>>>>> Jake Smith
>>>>> [t] @jakefolio
>>>>> [w] www.jakefolio.com
>>>>> On Mon, Nov 16, 2009 at 1:26 PM, Adam Jensen <jazzsli...@gmail.com>wrote:
>>>>>> Jake,
>>>>>> Right, that's what I'd like to have happen. Ideally at the controller
>>>>>> layer I could just issue a typical $table->findAll() call, and then the
>>>>>> event listener would filter out the impermissible records so that only the
>>>>>> permissible ones were returned.
>>>>>> Doesn't look like there is a postDqlSelect() hook available in
>>>>>> Doctrine_Record_Listener; that sounds like it'd be perfect, but I don't
>>>>>> think it's available.
>>>>>> I did try using Doctrine_Record_Listener::postHydrate(), since within
>>>>>> that context the $event->data property contains the fully-hydrated object
>>>>>> (which is what my ACL implementation needs to see). Problem is, I don't
>>>>>> think postHydrate is really designed such that you can prevent the object
>>>>>> from being returned altogether; also, it doesn't seem to run exactly when
>>>>>> I'm expecting it to …I may be able to post an example of what I mean later,
>>>>>> can't really work on it at the moment.
>>>>>> I'm also going to look into postFetchAll() later when I get the
>>>>>> chance; looks promising, but I don't know if it'll have the hydrated object
>>>>>> available yet. Maybe I'm going about all this the wrong way :)
>>>>>> Thanks!
>>>>>> Adam
>>>>>> On Mon, Nov 16, 2009 at 1:00 PM, Jake Smith <jake.smit...@gmail.com>wrote:
>>>>>>> Adam,
>>>>>>> From a logic standpoint wouldn't you want to NOT return
>>>>>>> records/results if the user does not have permission to see? Also, couldn't
>>>>>>> you do postDqlSelect if you want to handle this post select?
>>>>>>> Jake Smith
>>>>>>> [t] @jakefolio
>>>>>>> [w] www.jakefolio.com
>>>>>>> On Mon, Nov 16, 2009 at 12:43 PM, Adam Jensen <jazzsli...@gmail.com>wrote:
>>>>>>>> Jake,
>>>>>>>> Looked into that a bit, and put it into practice on, e.g., supplying
>>>>>>>> a default orderBy clause for all select queries against a particular
>>>>>>>> component…pretty cool stuff, really, since doing things like that at the
>>>>>>>> listener level will likely allow me to get rid of an entire abstraction
>>>>>>>> layer once I've got it all figured out.
>>>>>>>> However, what I'm looking to do in this instance is a bit different.
>>>>>>>> My access control logic is all in PHP (via Zend_Acl) rather than in the
>>>>>>>> database, so what I need to be able to do is filter out impermissible
>>>>>>>> results _after_ the query has already returned its result set. It's not
>>>>>>>> something I can really include in the query, unfortunately.
>>>>>>>> Any ideas on how to deal with that?
>>>>>>>> Thanks!
>>>>>>>> Adam
>>>>>>>> On Mon, Nov 16, 2009 at 10:38 AM, Jake Smith <
>>>>>>>> jake.smit...@gmail.com> wrote:
>>>>>>>>> Adam,
>>>>>>>>> Have you tried the preDqlSelect?
>>>>>>>>> You will need to extend the Doctrine_EventListener.
>>>>>>>>> http://www.doctrine-project.org/documentation/manual/1_1/en/event-lis...
>>>>>>>>> Jake Smith
>>>>>>>>> [t] @jakefolio
>>>>>>>>> [w] www.jakefolio.com
>>>>>>>>> On Fri, Nov 13, 2009 at 1:38 PM, jazzslider <jazzsli...@gmail.com>wrote:
>>>>>>>>>> Hello!
>>>>>>>>>> Gonna post this to the official Doctrine mailing list as soon as
>>>>>>>>>> my
>>>>>>>>>> membership there is approved, but I figured since we talked about
>>>>>>>>>> Doctrine this past week it's fair game to ask here too :)
>>>>>>>>>> In one of my projects, I'm trying to do application-level access
>>>>>>>>>> control on my Doctrine models. My access control system isn't
>>>>>>>>>> database-driven so I can't really include access control logic at
>>>>>>>>>> the
>>>>>>>>>> query level …as a result, I'm having to perform the checks after
>>>>>>>>>> the
>>>>>>>>>> query has returned all its data, like so:
>>>>>>>>>> [code]
>>>>>>>>>> // for single records...
>>>>>>>>>> $record = $doctrineTable->find($id);
>>>>>>>>>> if (!$acl->isAllowed($role, $record, 'read')) {
>>>>>>>>>> // Set $record to false so it will appear to later code
>>>>>>>>>> // as though the requested record was not found.
>>>>>>>>>> $record = false;
>>>>>>>>>> }
>>>>>>>>>> // for multiple records...
>>>>>>>>>> $records = $doctrineTable->findAll();
>>>>>>>>>> foreach ($records as $key => $record) {
>>>>>>>>>> if (!$acl->isAllowed($role, $record, 'read')) {
>>>>>>>>>> // Remove the record from the collection so it will
>>>>>>>>>> // not show up to later code.
>>>>>>>>>> $records->remove($key);
>>>>>>>>>> }
>>>>>>>>>> }
>>>>>>>>>> [/code]
>>>>>>>>>> Now, this works, but it gets pretty danged repetitive, and has a
>>>>>>>>>> tendency to muddy up my controller scripts significantly. I'd
>>>>>>>>>> much
>>>>>>>>>> rather allow the model layer to handle this directly, and after
>>>>>>>>>> Jake's
>>>>>>>>>> presentation on Tuesday I realized that Doctrine's event and
>>>>>>>>>> record
>>>>>>>>>> listeners might be able to help.
>>>>>>>>>> I've managed to get things working for other kinds of operations
>>>>>>>>>> (create, update, and delete) by using the preInsert(),
>>>>>>>>>> preUpdate(),
>>>>>>>>>> and preDelete() methods of Doctrine_Record_Listener like so:
>>>>>>>>>> [code]
>>>>>>>>>> class My_Doctrine_Record_Listener_Acl extends
>>>>>>>>>> Doctrine_Record_Listener
>>>>>>>>>> {
>>>>>>>>>> public function __construct($acl, $currentRole);
>>>>>>>>>> public function preInsert(Doctrine_Event $event)
>>>>>>>>>> {
>>>>>>>>>> $resource = $event->getInvoker();
>>>>>>>>>> if (!$this->getAcl()->isAllowed($this->getCurrentRole(),
>>>>>>>>>> $resource, 'create')) {
>>>>>>>>>> throw new My_Model_Exception('You are not allowed to do
>>>>>>>>>> that.');
>>>>>>>>>> }
>>>>>>>>>> }
>>>>>>>>>> // and then preUpdate and preDelete too
>>>>>>>>>> }
>>>>>>>>>> [/code]
>>>>>>>>>> However, for the "read" operations in my first code sample, I'm
>>>>>>>>>> having
>>>>>>>>>> trouble figuring out a couple of things. First off, I'm not sure
>>>>>>>>>> what
>>>>>>>>>> listener method to implement…it's a bit difficult to tell which
>>>>>>>>>> listener is firing when, and the documentation is a bit terse.
>>>>>>>>>> Second, I'm not really sure what to do once I've got the listener
>>>>>>>>>> figured out …the desired behavior is that if a record isn't
>>>>>>>>>> allowed
>>>>>>>>>> for the role defined in the listener object, the table will simply
>>>>>>>>>> return whatever it would've returned if that record didn't even
>>>>>>>>>> exist
>>>>>>>>>> in the first place.
>>>>>>>>>> I've tried this out in the Doctrine_Record_Listener::postHydrate()
>>>>>>>>>> hook, since I figured that would definitely get called for every
>>>>>>>>>> record returned by any given SELECT query …but it doesn't always
>>>>>>>>>> seem
>>>>>>>>>> to fire when I expect it to (e.g., if I call $table->findAll(), it
>>>>>>>>>> doesn't seem to be firing for the records being returned), and
>>>>>>>>>> when it
>>>>>>>>>> does fire, I don't know how to tell it not to return the
>>>>>>>>>> record…maybe
>>>>>>>>>> set $event->data to NULL, or call $event->skipOperation()?
>>>>>>>>>> Anyway, I thought I'd go ahead and ask if anyone had run into this
>>>>>>>>>> kind of use case before, or if there's a better approach I'm not
>>>>>>>>>> considering. Any ideas?
>>>>>>>>>> Thanks!
>>>>>>>>>> Adam
>>>>>>>>>> --
>>>>>>>>>> You received this message because you are subscribed to the Google
>>>>>>>>>> Groups "DallasPHP" group.
>>>>>>>>>> To post to this group, send email to dallasphp@googlegroups.com.
>>>>>>>>>> To unsubscribe from this group, send email to
>>>>>>>>>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>>>>>>>>>> .
>>>>>>>>>> For more options, visit this group at
>>>>>>>>>> http://groups.google.com/group/dallasphp?hl=.
>>>>>>>>> --
>>>>>>>>> You received this message because you are subscribed to the Google
>>>>>>>>> Groups "DallasPHP" group.
>>>>>>>>> To post to this group, send email to dallasphp@googlegroups.com.
>>>>>>>>> To unsubscribe from this group, send email to
>>>>>>>>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>>>>>>>>> .
>>>>>>>>> For more options, visit this group at
>>>>>>>>> http://groups.google.com/group/dallasphp?hl=.
>>>>>>>> --
>>>>>>>> You received this message because you are subscribed to the Google
>>>>>>>> Groups "DallasPHP" group.
>>>>>>>> To post to this group, send email to dallasphp@googlegroups.com.
>>>>>>>> To unsubscribe from this group, send email to
>>>>>>>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>>>>>>>> .
>>>>>>>> For more options, visit this group at
>>>>>>>> http://groups.google.com/group/dallasphp?hl=.
>>>>>>> --
>>>>>>> You received this message because you are subscribed to the Google
>>>>>>> Groups "DallasPHP" group.
>>>>>>> To post to this group, send email to dallasphp@googlegroups.com.
>>>>>>> To unsubscribe from this group, send email to
>>>>>>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>>>>>>> .
>>>>>>> For more options, visit this group at
>>>>>>> http://groups.google.com/group/dallasphp?hl=.
>>>>>> --
>>>>>> You received this message because you are subscribed to the Google
>>>>>> Groups "DallasPHP" group.
>>>>>> To post to this group, send email to dallasphp@googlegroups.com.
>>>>>> To unsubscribe from this group, send email to
>>>>>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>>>>>> .
>>>>>> For more options, visit this group at
>>>>>> http://groups.google.com/group/dallasphp?hl=.
>>>>> --
>>>>> You received this message because you are subscribed to the Google
>>>>> Groups "DallasPHP" group.
>>>>> To post to this group, send email to dallasphp@googlegroups.com.
>>>>> To unsubscribe from this group, send email to
>>>>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>>>>> .
>>>>> For more options, visit this group at
>>>>> http://groups.google.com/group/dallasphp?hl=.
>>> --
>>> You received this message because you are subscribed to the Google Groups
>>> "DallasPHP" group.
>>> To post to this group, send email to dallasphp@googlegroups.com.
>>> To unsubscribe from this group, send email to
>>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>>> .
>>> For more options, visit this group at
>>> http://groups.google.com/group/dallasphp?hl=.
>> --
>> You received this message because you are subscribed to the Google Groups
>> "DallasPHP" group.
>> To post to this group, send email to dallasphp@googlegroups.com.
>> To unsubscribe from this group, send email to
>> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
>> .
>> For more options, visit this group at
>> http://groups.google.com/group/dallasphp?hl=.
> --
> You received this message because you are subscribed to the Google Groups
> "DallasPHP" group.
> To post to this group, send email to dallasphp@googlegroups.com.
> To unsubscribe from this group, send email to
> dallasphp+unsubscribe@googlegroups.com<dallasphp%2Bunsubscribe@googlegroups .com>
> .
> For more options, visit this group at
> http://groups.google.com/group/dallasphp?hl=en.