__call equivalent for public methods
Asked Answered
G

2

10

I have an API for interacting with my web app, defined by a class. Each publicly accessible method needs to have authentication done before running. Rather than putting the same line over and over in each method, I'd like to use the magic __call function. However, it will only work on private or protected methods, and mine need to be public in order to work with Zend_Json_Server.

class MY_Api
{
  public function __call($name, $arguments)
  {
    //code here that checks arguments for valid auth token and returns an error if false
  }

  public function myFunction($param1, $param2, $param3)
  {
    //do stuff when the user calls the myFunction and passes the parameters
    //this function must remain public so that Zend_Json_Server can parse it
    //but I want it intercepted by a magic method so that the authentication
    //can be checked and the system bails before it even gets to this function.
  }
}

Is it possible to hook into these public functions and possibly cancel their execution before they are called?

Garlaand answered 13/7, 2011 at 20:47 Comment(3)
Not yet. I wasn't sure it was appropriate to use for this section of the application. It's a rest API, so they're not logging in and setting up sessions but instead passing a token.Garlaand
What is calling the public method and can that caller invoke the authentication method?Genethlialogy
@Genethlialogy the caller is the Zend_Json_Server->handle() method, so I'd have to extend the Zend_Json_Server class, which seems like overkill for being able to do this.Garlaand
B
9

__call actually works for all methods, including public. However, the reason it won't work if the public method already exists, is because code outside your class can already access the public members. __call is only invoked for members which aren't accessible by the calling code.

As far as I know there are really no options to do what you're looking for, except by using some kind of a decorator pattern:

class AuthDecorator {
    private $object;

    public function __construct($object) {
        $this->object = $object;
    }

    public function __call($method, $params) {
        //Put code for access checking here

        if($accessOk) {
            return call_user_func_array(array($this->object, $method), $params);
        }
    }
}

$api = new MY_Api();
$decoratedApi = new AuthDecorator($api);

//any calls to decoratedApi would get an auth check, and if ok, 
//go to the normal api class' function
Bandoline answered 13/7, 2011 at 20:57 Comment(5)
The solution here is classic Aspect Oriented Programming using a proxy and exactly what you need. I would quibble that since __call() is called only when a method is inaccessible and that all public methods are accessible, __call() can be considered not to work with public methods. You got me excited thinking there might be a loophole I had missed, but alas that was not the case. :(Mannos
Or renaming all functions to add an _ or so to the name. You can call method x, which will go through __call, which will then call the public method x_. Now I think about it, a decorator is probably be easier. :)Shantae
@David Harkness Ah, thanks - Aspects was exactly the other thing I was going to mention but I could only think of traits :) But yes, you're quite right about __call from that perspective.Bandoline
I like this idea, but I think Zend_Json_Server might be getting in my way on its feasability because it uses reflection to determine which methods are accessible. I think wrapping the decorator around my class will mask the available method. @Genethlialogy suggested looking up a level, so perhaps I'll put the checking around the $zendjsonserver->handle() call and nip it in the bud before it even gets to the class. This can't be the best way to do it, but the reflection part of the zend_json_server isn't giving me many options.Garlaand
I gave this answer credit because in most situations it WOULD be the right answer. Because I needed to pass Zend_Rest_Server the class with my functions so that it could do reflection, it wouldn't work for me. I ended up moving my authentication code up into the controller so that it would check before calling the handle() method, which solved the problem.Garlaand
G
3

Since you went with my comment as a possible solution, I've formatted it into an answer for posterity:

If aspect-oriented or decorator solutions don't work for you, you can try a more framework-based solution by placing the the code that checks authentication either in the caller of your public method or even higher up.

Genethlialogy answered 13/7, 2011 at 22:18 Comment(3)
I haven't used Zend_Json_Server before, but its kissing cousin Zend_Controller_Action defines preDispatch() which is called before the actual action method. Perhaps the JSON component supplies such a feature.Mannos
nice idea, @David, but Zend_Json_Server doesn't and it extends Zend_Server_Abstract, which doesn't either.Garlaand
Thanks for making it an answer @webbiedave. This did indeed end up solving the problem for this particular case.Garlaand

© 2022 - 2024 — McMap. All rights reserved.