Please upgrade here. These earlier versions are no longer being updated and have security issues.
HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

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

businessdadbusinessdad Stealth contributor MVP
edited March 2012 in Vanilla 2.0 - 2.8

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.

«1

Answers

  • x00x00 MVP
    edited March 2012

    -

    grep is your friend.

  • businessdadbusinessdad Stealth contributor MVP

    @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.

  • 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.

  • businessdadbusinessdad Stealth contributor MVP

    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.

  • businessdadbusinessdad Stealth contributor MVP

    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.

  • businessdadbusinessdad Stealth contributor MVP

    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.

  • businessdadbusinessdad Stealth contributor MVP

    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.

  • businessdadbusinessdad Stealth contributor MVP

    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.

  • businessdadbusinessdad Stealth contributor MVP

    @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.

  • businessdadbusinessdad Stealth contributor MVP
    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.

Sign In or Register to comment.