Many of you will have run into this problem: You want to stash a callback in Catalyst that will build you the URL for an action. It’s supposed to be a callback, because you want to dynamically pass an ID or other argument to the URL.
As nice as this sounds, you have to be careful so you don’t create a circular reference. I’ll discuss the current best practice for this problem below and will propose another kind of solution.
How you should do it now
Currently, the best practice is to use the
ContextClosure component trait. It will provide
your controller with a new method called make_context_closure.
To create a URL builder callback using uri_for_action (which I
hope you are using already) you’d do something like this:
$ctx->stash(uri_for_post => $self->make_context_closure(sub {
my ($ctx, $id) = @_;
$ctx->uri_for_action('/post/view', [$id]);
}, $ctx));
This solves the problem nice and elegantly. You pass in your code reference and original context and it returns a code reference that weakly references the context object and will pass it into your code reference everytime it’s called.
However, I find that rather wordy if I have a controller that uses
many callbacks. Also, one might forget to use make_context_closure
and simply pass in a closure or something else referencing the
context object by accident.
And besides, I’ve always been bugged by having code in the controller that’s more utility than actual controller logic.
How one might do it
The core of the matter is that if you stash something that holds a reference to the context object, you have a circular reference. In the case of the code reference this is:
- The context object, which references …
- the stash data structure, which references …
- the code reference, which references …
- the context object, …
The obvious and common solution to this problem is introducing a weak link in the chain. In the above example it is done by a second code reference that only holds a weakened reference. This essentially means the link exists between the items 2 and 3 on the above list. It would look like this:
- The context object, which references …
- the stash data structure, which references …
- the generated callback wrapper, which weakly references …
- the code reference, which references …
- the context object, …
I thought I’d take another route and insert the weak link between 3 and 4 instead. That is, directly between the referencing structure and the context object.
This is done by wrapping the context object with a proxy that will hold a weak reference to the original context, and will delegate all method calls to it. The chain would then look something like this:
- The context object, which references …
- the stash data structure, which references …
- the code reference, which references …
- the proxy object, which weakly references …
- the context object, …
Of course, this proxy must only be passed into controller-space. But that’s the only place where it is useful anyway.
Code Examples
Alright, enough with the boring theory. How would this look like in action? There are two distributions that I currently only have on GitHub.
One is Object::WeakProxy. This implements the basic proxy object class that holds the weak reference and delegates all calls.
The other one is Catalyst::TraitFor::Controller::WeakContext. This controller trait can be composed into any modern Catalyst controller. It will modify the controller so that the context object that is passed to your actions is already a weak proxy. A full example would look like this:
package MyApp::Controller::Foo;
use Moose;
BEGIN {
extends 'Catalyst::Controller';
with 'Catalyst::TraitFor::Controller::WeakContext';
}
sub bar: Chained('/') Args(0) {
my ($self, $ctx) = @_;
$ctx->stash(uri_for_post => sub {
$ctx->uri_for_action('/post/view', [shift]);
});
}
1;
As you can see, since the $ctx variable holds no longer the context object,
but rather the proxy that weakly references it, you don’t need to pass the
code reference to a currying generator. It also feels a lot more intuitive to
me. And there’s also no chance of accidentally creating a circular reference
by stashing something referencing the context object.
The two above modules have tests and small documentation (how much can one write about this?), but aren’t yet on CPAN. I’d like to hear people’s comments before releasing it.
Also, I find it a bit odd that there isn’t such a component yet. Maybe I just missed it. If so, please tell me!




Leave a comment
All comments are moderated. Spammers don't waste your time