Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Sign In with Facebook Sign In with Google Sign In with OpenID Sign In with Twitter
Support for Vanilla Forums Cloud product

In this Discussion

Follow Us


Is it possible to retrieve a list of all methods added to a class?

edited March 2012 in Questions

I was reading about Magic Methods in Plugin Quick-Start Guide, and I was wondering, is there a way to see al the methods that have been "tacked" to a class? A simple get_class_methods() doesn't work, as these new methods are not shown.

Thanks in advance for the help.

Best Answer

  • x00x00 MVP
    Answer ✓

    magic method work like this:

    there is an attempt to use a method that doesn't exist. there is a check for the special __call method. the method name plus an array of parameters is passed to this. Further logic inside.

    grep is your friend.

Answers

  • x00x00 MVP
    edited March 2012

    -

    grep is your friend.

  • @x00: I'm talking about "Magic Methods", i.e. the ones created by declaring them as Controller_MethodName_Create.

  • magic method are by definition called on the fly.

    you would have to grep plugins for the controller an the create.

    grep is your friend.

  • A-ha! Now I understand, it seems I'll have to do a bit more work then. Thanks for your help! :)

  • vanilla magic methods refer to the use of __call, what is passed. In PHP it refers tot he special method used for overloading __call, __callStatic, __get, __set, __isset, __unset, (the latter four are used on 'magic' properties).

    http://php.net/manual/en/language.oop5.overloading.php

    grep is your friend.

  • Argh! I was so close! I implemented a method that could return a list of the Magic Methods added to a class, but the list is declared as "private", therefore I can't access it. If nothing else, I learned that such Magic Methods have some important limitations, compared to "normal", declared methods. :)

  • php is interpreted, there is also no open classes like ruby.

    class String
      def foo
       "foo"
      end
    end 
    

    this is extending the string class.

    grep is your friend.

  • I know. In my case, I'd need to access a private property Gdn_PluginManager->_NewMethodCollection to retrieve all the Magic Methods associated to a class. My target would be to loop through all of them and call them one by one.

    What I'm trying to achieve is a functionality similar to a global "Cron", where a plugin can declare a Cron method (e.g. Cron_MyPlugin_Create()) and have it called regularly (this in its simplest version, obviously there's much more that can be done from there).

    Unfortunately, the only way I managed to implement it successfully so far has been by hacking class Gdn_PluginManager and exposing property _NewMethodCollection, either by declaring it public or by using a "Get" method.

    Of course, I don't want to rely on such a hack, as, if I really had to modify the Core, it would be much cleaner to simply implement a GetClassNewMethods method straight into Gdn_PluginManager class.

  • x00x00 MVP
    edited March 2012

    Ah I wouldn't do it this way.

    instead have a method to register the cron. Use call_user_func/call_user_func_array within it.

    You can pass an object an its method like so array($object,'method'); or array($this,'method');

    grep is your friend.

  • overriding creates overhead it needs to be used wisely.

    grep is your friend.

  • Thanks @x00, I was, in fact, reviewing the design to make it simpler. Probably a method to register the Cron would be the best.

    At the moment, I created a method called CronJobs->RegisterCronJob(). In another plugin, I call it as follows:

    $CronPluginInstance = Gdn::PluginManager()->GetPluginInstance('CronJobsPlugin');
    if(isset($CronPluginInstance)) {
      $CronPluginInstance->RegisterCronJob($this, 'CronJob');
    }
    

    Is that "orthodox" enough, or is there a better way? I'd love to be able to just call "RegisterCronJob" from any plugin, but issue is that I can't assume it's installed and enabled.

  • x00x00 MVP
    edited March 2012

    You could go with object, method, additional params (array).

    I think what you want is for the cron plugin to register it own cron. That is the most logical way of doing it. Though actually it would be better with events.

    $this->EventArguments['UsefulStuff']=$UsefulStuff;
    $this->FireEvent('CronJob');
    

    they would make a cron

    public function CronJobsPlugin_CronJob_Handler($Sender,$Args){
       ....
    }
    

    where CronJobsPlugin is the plugin that has the CronJob event.

    grep is your friend.

  • Thanks again. I'm not sure I understand the $this->EventArguments['UsefulStuff']=$UsefulStuff part. What I actually want to do is to allow my Cron plugin to call other plugins methods, which are stored in a list somewhere. This way, if a new plugin would need to run something periodically, it will just have to register itself with the Cron Plugin, which, in turn, will call the callback method at each execution.

    I reckon it would be done with Events too: by firing a "CronJob" event, any 3rd party plugin would just have to implement a handler and do some stuff in it. However, this means that all Cron Event Handlers will run unconditionally. Forcing a plugin to register a method would allow some sort of control over the Cron Events (for example, some could be enabled or disabled, depending on the circumstances). This is how I implemented it at the moment (I must say it was easier than I thought, even considering that I took the "wrong route" multiple times).

    In short, I see there are many ways of doing it, and there isn't an "absolutely better" way.

  • no it is just anything you want to pass through the $Args. It also has the original sender object, which in this case you be the CronJobsPlugin instance. So you could have public methods and properties that they could use.

    grep is your friend.

  • @x00: I'm sorry, but I'm not sure I follow you. Let me see if I understand.

    1- CronPlugin would fire an event, such as RegisterCron. 2- Other plugins that would like to be registered a method to run during a Cron execution, would implement MyPlugin_RegisterCron_Handler. Such handler would receive some information needed by the client plugin to register (i.e. the Sender, which is my CronPlugin and, eventually, other arguments). 3- Client plugin uses the information in the handler to register itself. 4- CronPlugin will now have a set of methods to call during Cron execution.

    The above means that, if a 3rd party plugin is installed, but CronPlugin is not, its method MyPlugin_RegisterCron_Handler is not getting called, but the plugin works anyway.

    Did I get it right?

  • x00x00 MVP
    edited March 2012

    almost

    MyPlugin_RegisterCron_Handler should be CronPlugin_RegisterCron_Handler

    what you are actually doing is defining what the cron entails, but since it is a cron, you need to pass the necessary info. Say for instance a request is your basic unit, you would need to pass the algorithm/formula that determines it is time to perform that task (it can be quite intelligent and factor in many things). It can also help if there was a model reference in CronPlugin, which can be passed to the cron method provide a way of storing values to check later.

    You also need the payload which is the task itself. This can be passed as an object method, a static method, or a standalone function.

    So you could have an convenience AddTask method perhaps. It could add to an indexed collection(s) of task and the cron formula.

            static public function OnFifthOfMonth($Cron){
    
                if($Cron->Model->Get('MyPlugin','FifthOfMonth')Model->Set(('Plugins.MyPlugin.Month',strtotime('YYYY-MM-05'));
                        return true;
                             }else{
                        return false;
                    }
                }else{
                    return false;
                }
            }
    
            public function MyTask($Cron, $Args){
                //do something
            }
    
            public function CronPlugin_RegisterCron_Handler($Sender){
                 $Sender->AddTask(array('MyPlugin','OnFifthOfMonth'), array($this, 'MyTask'), $AnyArgs);
            }
    
    

    then your CronPlugin will be checking every basic unit (such as every request), loop through the task to see if anything need to be performed and if so calling the method.

    you can also specify what other plugins are required, when you create a plugin in the PluginInfo. It shouldn't enable through the dashboard otherwise.

    If you want a proper cron, i.e. somethign that is running the background regardless of the requests, then you need to be able to interface with and support such infrastructure, it is a little bit more tricky if you are hoping to have a distributed solution.

    grep is your friend.

  • edited March 2012

    I think I understand now. The code you wrote goes into the 3rd party plugin, right? So, workflow would be the following:
    1- Cron Plugin fire RegisterCron event.
    2- Client Plugin implements a handler to register itself. Here it can pass the method to execute and, optionally, another method that Cron Plugin will run to determine if it's time to execute the first or not.
    3- Cron Plugin will run the Execute method, loop through all the registered plugins, executes the "should I run it?" method of each client plugin (if it exists) and, eventually, run the Cron method.

    Did I get it right, finally? :)

    Regarding the proper Cron, I could use a hybrid solution such as the one implemented in Drupal: expose a menu entry, accessible via URL, which would fire the Cron sequence. Such menu entry would be secured, so that only localhost could call it (this is to prevent DDoS attacks). Additionally, some throttling mechanism could be implemented as well, but we're going into a lot of detail now.

  • the code is not formatting correctly. sorry. bare with.

    grep is your friend.

  • ah this is a real pain in the ass. There are problem with the multi formatter. It is jack of all trades....

    grep is your friend.

  • x00x00 MVP
    edited March 2012

    the function was meant to be:

    static public function OnFifthOfMonth($Cron){
                if($Cron->Model->Get('MyPlugin','FifthOfMonth')<strtotime('YYYY-MM-05') && intval(date('d'))==5){
                    $Cron->Model->Set('MyPlugin','FifthOfMonth',strtotime('YYYY-MM-05'));
                    return true;
                }else{
                    return false;
                }
    }

    grep is your friend.

  • ok I hope that is clearer.

    grep is your friend.

  • sorry I was tired last night it should be strtotime(date('Y-m-05')) not strtotime('YYYY-MM-05');

    grep is your friend.

  • @x00: Thanks again for the example. However, I decided to take a simpler route for the moment. The Cron Plugin will simply fire all registered Cron tasks at every run. User will just have to schedule a call to the appropriate menu item (e.g. /cronplugin/execute) and that's it. I'm at the very beginning of designing it and I'm not even using a Model yet, just an associative array. Plenty of refactoring to be done, but, right now, it actually works! :)

    Thank you very much for your (late night) help. GMT time zone here, it was late for me too. :)

  • The model would be to give the cron task somewhere to store persistent key/values, however they could deal with what entire on their own, or simply not rely on any persistent data.

    I was tempted just to have a config store, but I though that was too crude of an example.

    grep is your friend.

  • It's a good idea to have something persistent, although the "Unregister" phase would have to be handled as well. For simplicity, I simply allow plugin to register themselves at runtime, it's anyway a fairly cheap operation (an "add" to an array). This way, if client plugin is active, its Cron will be called. If not, it will not be called. Easy. :)

  • x00x00 MVP
    edited March 2012

    well the register could be per prequest, it doesn't mean you are storing the register.

    However the task and the taskcondition may need some persistent data to work.

    Say you only want to cron on the 5 day of the month, but you don't want to be doing the task over over on the 5th you have to make a record the task has been performed.

    grep is your friend.

  • x00x00 MVP
    edited March 2012

    of course you could make a more exact taskcondition but as this is per request the is good chance you might miss the cron.

    grep is your friend.

  • x00 said: well the register could be per prequest, it doesn't mean you are storing the register.

    However the task and the taskcondition may need some persistent data to work.

    Say you only want to cron on the 5 day of the month, but you don't want to be doing the task over over on the 5th you have to make a record the task has been performed.

    @x00: you're spot on, the ability of deciding what to run and when would be great. However, for the moment, I won't let my CronPlugin handle this yet, it will be the next version. Every Cron Run will fire each and every registered event, every time, and it will be up to the plugin to decide what to do. After all, there aren't plugins using it yet, therefore there's no point in adding features before the core functionality has been tested. :)

    Next step will be "how to do Unit Testing in Vanilla". :)

Sign In or Register to comment.