PHP use private method as callback - php

I am experimenting with PHP+WP the very first time. I intend to use WP plugin hooks. As a C++ programmer I also intend to put all my code into classes. Currently I am kind of stuck with the following snippet that should install a WP plugin hook:
class SettingsHandler
{
public function __construct()
{
add_filter('plugin_action_links', array($this, 'AddSettingsLink'), 10, 2);
}
private function AddSettingsLink($links, $file)
{
if ($file==plugin_basename(__FILE__))
{
$settings_link = 'Settings';
array_unshift($links, $settings_link);
}
return $links;
}
}
$settingsHandler = new SettingsHandler();
This gives me an error message:
Warning: call_user_func_array() expects parameter 1 to be a valid callback, cannot access private method SettingsHandler::AddSettingsLink() in E:\xampp\apps\wordpress\htdocs\wp-includes\plugin.php on line 199
When I switch the callback to public, the error is gone. It seems I can not use a private method as a callback in PHP/WP. This would be very bad because it reveals a lot of callback methods that should not be accessed by anyone else directly. Can I make suchs callbacks private?
I also found the following snippet which runs fine:
class a
{
public function __construct()
{
$str = " test test ";
$result = preg_replace_callback('/test/', array($this, 'callback'), $str);
echo $result;
}
private function callback($m)
{
return 'replaced';
}
}
$a = new a();
Why does the second snippet work while the first fails? Where is the difference?

The second version works because preg_match_all is called from within the class scope and will execute the callback immediately.
But the add_filter function only adds the callback to the global $wp_filter array. The callbacks in that array are then called at a later stage from outside the class you defined the method in. Consequently, visibility rules apply making the callback inaccessible.
You can¹ get around this by wrapping the call to the method into an anonymous function, e.g.
public function __construct()
{
add_filter(
'plugin_action_links',
function($links, $file) {
return $this->AddSettingsLink($links, $file);
},
10,
2
);
}
However, this requires at least PHP 5.4 (see changelog in Manual) due to $this being unavailable in the anonymous function before that version.
Another option would be to have the SettingsHandler implement __invoke to turn it into a Functor, e.g. you add
public function __invoke($links, $file)
{
return $this->AddSettingsLink($links, $file);
}
and change the ctor code to
add_filter('plugin_action_links', $this, 10, 2);
Since the class implements __invoke as a public entry point, the instance can be used as callback. This way, you can keep the private stuff private.
On a side note, I'd move the code adding the filter outside the class. Having it in the ctor makes the class less testable (yeah, I know no one tests WP plugins, but still). Instead, put it into your plugin file and then include everything else required from there, e.g.
// your-plugin.php
include 'SettingsHandler.php';
add_filter('plugin_action_links', new SettingsHandler, 10, 2);
But that's up to you.
¹Note: apparently this doesn't work in Wordpress because it will try to serialize the closure somewhere along the way. In general, this a working pattern to provide private methods as callbacks.

Related

Can't access a protected function from another protected function within the same PHP Class

I looked a lot and didn't find a similar question so please bear with me I am still learning OOP.
I read that in a class, a protected function can be accessed from any other function, but it has to be within the same class. So why does the following code not work when I make the
register_scripts() function as protected? I mean, since setup_hooks() function is holding the action that will trigger the register_scripts() callback, and both these functions are in the same Assets class, so why the error on making it protected?
class Assets{
protected function __construct(){
// Load all classes
$this->setup_hooks();
}
// define hooks for the plugin here
protected function setup_hooks(){
// Actions
add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] );
}
public function register_scripts(){
wp_enqueue_script( 'mono_script', MY_DIR_URL . '/assets/script.js', array(), '1.0', true );
}
}
I get the following error:
Fatal error: Uncaught TypeError: call_user_func_array(): Argument #1 ($callback) must be a valid callback, cannot access protected method MWS_PLUGIN\Inc\Assets::register_scripts()
As #disinfor showed, this comes down to scope, and it isn't really a WordPress issue, but a PHP one. WordPress's hook system obviously doesn't live in your class, it lives in the global namespace (although that's not part of the issue) and that's the important distinction.
To your note about setup_hooks "holding the action", that isn't true, it is only referencing it. Whoever actually calls that reference must have the proper scope.
You can get around this, however, by using closures. Here's a sample of a class and a couple of ways to pass, along with the failure that you noticed.
class Thing {
public function publicFunction() {
echo __METHOD__, PHP_EOL;
}
protected function protectedFunction() {
echo __METHOD__, PHP_EOL;
}
public function __construct() {
// This works as expected
test([$this, 'publicFunction']);
// This all works because it is a closure
test(
function() {
$this->protectedFunction();
}
);
// This is a shorter version of the previous
test( fn() => $this->protectedFunction() );
// This does not work
test([$this, 'protectedFunction']);
}
}
function test(callable $func){
$func();
}
new Thing;
Demo: https://3v4l.org/mdiX0

Store but not EXECUTE a Global Function (PHP)

This is an odd situation and I think the answer is 'you can't do that, what are you thinking?' but hope someone can prove me wrong.
My goal is to store a globally scoped function in a variable then inject it for execution within a class object.
I would like to avoid using call_user_func() as this searches for the function in the global namespace and is the same effect as if I were to just execute the global function from within the class object. I would like my class to execute the object as if it were an internal class method, not an external function. This comes close but not quite.
I cannot modify the function or wrap it in a class.
(Why am I jumping through these hoops?) Needs to be used within this class to follow a spec.
I know I can just duplicate the function in the class and be done with it, but you know the issues with that (plus it creeps up on SRP.) Reflection would work perfectly but this function is not in a class, it is just out there in an include. I've tried wrapping it an anonymous function and the closure object doesn't execute the function.
Is there any hope to do this? The function is simple, accepts a scalar param, does some stuff to it, returns a value (and is tightly coupled with other code, cannot be moved or changed.)
function someFunction($param)
{
// do some stuff
return $someScalarValue;
}
What I would hope is something like
$func = someFunction([some value]); // doesn't work of course, this would store result in $func
$cls = new SomeClass($func);
Then a method in the class could run the function object, much like call_user_func but not have to search the global namespace.
protected function someThing()
{
$this->injected_function([some class value]); // also doesn't work of course
}
When you use $this you are in the objects instance scope. You could pass a (reference) method into the constructor.
$myFunc = function($arg) { var_dump($arg); return 314; };
class myClass {
private $func;
public function __construct($func) {
$this->func = $func;
}
public function do($value) {
$this->func->call($this, $value);
}
}
$var = 'Hello world!';
$myObj = new myClass($myFunc);
$value = $myObj->do($var); // $value is now 314
If you do not want the function to be stored in global namespace you can just pass even an anonymous function like this on the fly:
$myObj = new myClass(function($arg) { var_dump($arg); return 314; });
$value = $myObj->do($var); // $value is now 314
Thank you #Markkus Zeller for your comments, as I suspected there is no way to do what I originally was tasked, to "inject" a global function as an dependency. There is, but it only really works with anonymous functions.
After a lot of stressful pushback, I convinced our managers that wrapping this in a simple class was the way to go. This,
// require_once('some-function.php');
function someFunction($param)
{
// do some stuff
return $someScalarValue;
}
. . . now becomes this. (Typed out on the fly and may contain deficiencies, concept only)
// require_once('some-function.php');
require_once('path/to/SomeFunctionClass.php');
function someFunction($param)
{
$cls = new SomeFunctionClass($param);
return $cls->execute();
}
. . . where execute() contains identical code that was in someFunction(). I can now use "SomeFunctionClass" for a DI. There is more to the story, but that is the gist, and this one change can be implemented without modifying any of the 450 or so instances that use this global function (and each of those they can be gradually ported to use the new wrapper.) It also allows me to isolate and mock the functionality for unit testing.

Make a script pluginable

I am working on a script, and I need to make it pluginable. Now the syntax I have come with and which should work for me, is to make it use classes. For example, in order to create a new plugin that would be run when a certain point (hook) is reached, you would declare a new class. What I am not sure is how would the hook be specified in that syntax, so I am looking for suggestions.
Syntax example:
<?php
class ScriptPlugin
{
function runPlugin() {} // would be run when the time has to come to execute this plugin
}
?>
Also, if that syntax is not going to work, it would be great if you guys could give me a good syntax example.
There is the Observer Pattern which comes to mind. Plugins will register themselves and will get notifications when the hook is invoked.
Another thing that comes to mind are callbacks in PHP. And there was a similar question already with an answer that came to mind. It shows hooks based on callbacks.
The Observer Pattern runs a bit short because with hooks you often want to provide things like arguments and a return value. The linked answer which uses callbacks does not have this either, so I wrote a little Hooks example class that provides named hooks/events to registered callbacks, and a way to register your own classes, e.g. a plugin.
The idea is pretty basic:
A hook has a name and zero or more callbacks attached.
All hooks are managed in a Hooks class.
The main code invokes hooks by calling a function on the Hooks object.
Plugins (and other classes) can register their own callbacks, which is done with the help of the Registerable interface.
Some example code with one plugin and two hooks:
<?php
Namespace Addon;
class Hooks
{
private $hooks = array();
private $arguments;
private $name;
private $return;
public function __call($name, array $arguments)
{
$name = (string) $name;
$this->name = $name;
$this->arguments = $arguments;
$this->return = NULL;
foreach($this->getHooks($name) as $hook)
{
$this->return = call_user_func($hook, $this);
}
return $this->return;
}
public function getHooks($name)
{
return isset($this->hooks[$name]) ? $this->hooks[$name] : array();
}
public function getArguments()
{
return $this->arguments;
}
public function getName()
{
return $this->name;
}
public function getReturn()
{
return $this->return;
}
public function setReturn($return)
{
$this->return = $return;
}
public function attach($name, $callback)
{
$this->hooks[(string) $name][] = $callback;
}
public function register(Registerable $plugin)
{
$plugin->register($this);
}
}
interface Registerable
{
public function register(Hooks $hooks);
}
class MyPlugin implements Registerable
{
public function register(Hooks $hooks)
{
$hooks->attach('postPublished', array($this, 'postPublished'));
$hooks->attach('postDisplayFilter', array($this, 'filterToUpper'));
}
public function postPublished()
{
echo "MyPlugin: postPublished.\n";
}
public function filterToUpper(Hooks $context)
{
list($post) = $context->getArguments();
return strtoupper($post);
}
}
$hooks = new Hooks();
$plugin = new MyPlugin();
$hooks->register($plugin);
$hooks->postPublished();
echo $hooks->postDisplayFilter("Some post text\n");
I've done it this way to prevent that each Plugin must have a concrete base class only because it wants to make use of hooks. Additionally everything can register hooks, the only thing needed is a callback. For example an anonymous function:
$hooks->attach('hookName', function() {echo "Hook was called\n";});
You can however create yourself a plugin base class, that for example implements the register function and will automatically register functions that have a certain docblock tag or the name of a function
class MyNewPlugin extends PluginSuper
{
/**
* #hook postPublished
*/
public function justAnotherFunction() {}
public hookPostPublished() {}
}
The superclass can make use of Reflection to add the hooks on runtime. However reflection can slow things down and might make things harder to debug.
Let's say a plugin is like :
class NewsPlugin extends Plugin
{
function onCreate($title)
{
# Do some stuff
}
}
Then when you create a news you can just call onCreate on all plugins registered.
I would make a base abstract class with functions for all the hooks that could possibly be called.
abstract class Plugin {
abstract function yourHook();
}
All plugin classes should inherit this base class, and will override those base functions with their own.
class SomePlugin extends Plugin {
function yourHook() {
echo 'yourHook() Called!';
}
}
Now when your program runs, you need to find all of those plugin files to include, and somehow put them into an array, such as $plugins. See this article: https://stackoverflow.com/a/599694/362536
foreach (glob("classes/*.php") as $filename)
{
include $filename;
}
(From Karsten)
Define a function accessible from everything, such as registerPlugin():
function registerPlugin($classname) {
$plugins[] = new $classname();
}
Make the top line of each plugin file like this (prior to the class):
registerPlugin('SomePlugin');
If you do this, you'll have an array in $plugins with instances of each plugin. At the appropriate time, you can do something like this:
foreach ($plugins as $plugin) {
$plugin->yourHook();
}
As an alternative, it may be more appropriate to use interfaces in your case, instead. You should decide which method is best for your application.

PHP5: Callbacks between Class Objects

I am trying to understand how far I can go with PHP5's closures/callbacks, but I am currently trapped in a glass case of "why doesn't this work".
In the following example, I understand that the use of $this in a callback (especially when the scope changes) isn't going to work, it's just there to show you how I hope to be able to use callbacks/closures.
class Customer {
public $name = '';
public $callback = NULL;
function __construct($name) {
$this->name = $name;
}
function when_enters($callback) {
$this->callback = $callback;
}
function enter_store() {
if(is_callable($this->callback))
call_user_func($this->callback);
}
}
class Salesman {
public $customer = NULL;
function add_customer(&$customer) {
$this->customer =& $customer;
$this->customer->when_enters(function() {
$this->greet_customer();
});
}
function greet_customer() {
echo "Hello, {$this->customer->name}!";
}
}
$salesman = new Salesman();
$customer = new Customer('John');
$salesman->add_customer(&$customer);
$customer->enter_store();
I have been able to reproduce this basic functionally by implementing Salesman as a static class and setting the callback function as Salesman::greet_customer instead of $this->greet_customer().
Basically, what I want to know is... using object instances, is this kind of functionality possible?
In php, call_user_func can accept a two-element array to call a method on a class. So if you do this:
$this->customer->when_enters(array($this,'greet_customer'));
it will do what you want. Another alternative on PHP 5.3.0 or greater is to use a closure along with a local copy of $this:
$this_copy=$this;
$this->customer->when_enters(function() use ($this_copy) {
$this_copy->greet_customer();
});
I have some good news, and some bad news.
The good news is that the next major release of PHP (5.4?) will permit anonymous functions to be properties of a class, and be callable without jumping through hoops, and will allow you to reference $this by binding the function to a specific context.
The bad news is that nobody seems to know when the PHP trunk will be turned into a release.
Now, given that you can't actually reference $this inside the anonymous function, what you can do here is very limited. One option would be to pass the current object to the function:
function enter_store() {
if(is_callable($this->callback))
call_user_func($this->callback, $this);
}
While this will work, and allow you to poke at the object from the function, you'd be limited to methods and properties labeled public. This may or may not be an issue for you.

PHP isset($this) and using the same object method in a static and object context

I'm working on a class which needs to be accessible via static function calls as well as object methods. One thing I have found is that I'm duplicating logic across multiple functions.
Simplified example:
class Configurable{
protected $configurations = array();
protected static $static_configurations = array();
public function configure($name, $value){
// ...lots of validation logic...
$this->configurations[$name] = $value;
}
public static function static_configure($name, $value){
// ...lots of validation logic (repeated)...
self::$static_configurations[$name] = $value;
}
}
I've found a solution to this, but it feels really dirty:
class Configurable{
protected $configurations = array();
protected static $static_configurations = array();
public function configure($name, $value){
// ...lots of validation logic...
if (isset($this)){
$this->configurations[$name] = $value;
}
else{
self::$static_configurations[$name] = $value;
}
}
}
I need the static function as well so that I can set configurations throughout the application. Also, the nice thing with this technique is that I can use the same method names in both scopes.
Are there any problems with testing scope like this? Performance issues, forward compatibility issues, etc. It all works for me on PHP 5.2, and I don't need to support <5.
The issue with the second method is that it will result in an error when error reporting is set to E_STRICT. For example:
Strict standards: Non-static method Foo::bar() should not be called statically in /home/yacoby/dev/php/test.php on line 10
A point with PHP6 is that the E_STRICT errors are moved to E_ALL. In other words E_ALL will cover all errors including not allowing you to call non static methods statically.
An alternative method may be to move the validation logic to a static function. That way the non static function and the static function can call the validation logic.
Static methods would require a different number of arguments than their objective counterpart - the additional argument would be an execution context. If there's no execution context, then it only makes sense to call it statically.
My preferred approach given that I'm building a library with multiple interfaces like this, is to create a static class and a dynamic class. Have one proxy the calls to the other. For example:
class DynamicClass {
protected $foo;
protected $bar;
public function baz($arg1) {
return StaticClass::bar($this->foo, $arg1);
}
public function zop($arg1, $arg2) {
return StaticClass::zop($this->foo, $this->bar, $arg1, $arg2);
}
// Context-less helper function
public function womp($arg1) {
return StaticClass::womp($arg1);
}
}
class StaticClass {
public static function baz(&$fooContext, $arg1) { ... }
public static function zop(&$fooContext, &$barContext, $arg1, $arg2) { ... }
public static function womp($arg1) { ... }
}
It's up to you exactly how you pass context to the static class - you'll have to do whatever makes sense for you. The work done in most functions should be pretty minor (if you're doing a lot, then you probably should be breaking the work up into smaller functions as a rule), and so should only require a handful of context arguments. Or you could create a full context array and pass that around everywhere (either populating it in DynamicClass just before each call, or else track all DynamicClass properties in that array so you can quickly & easily pass it around.
Though actually it looks like you might benefit from a Singleton design pattern. From what I can see, you're trying to create a global Configurable, and also have the option to create individual local Configurables. With the singleton design pattern, you create a globally accessible version of a class that you can guarantee you only have one of (without breaking OOP design principles and having to rely on $_GLOBALS etc). For example:
class DynamicClass {
protected $foo;
protected $bar;
public function baz($arg1) { ... }
public function zop($arg1, $arg2) { ... }
public static function getSingleton() {
static $instance = null;
if ($instance === null) $instance = new DynamicClass();
return $instance;
}
}
No matter where in your code you are, you can get access to the same instance with DynamicClass::getSingleton(). You also have the option of creating one-off non-singleton versions. You essentially get the best of both worlds while only having to write all your methods with dynamic access in mind exclusively.
I don't find it so absurd to allow calling a method on an instance and statically as well. My case:
TestRecord::generateForm(); // Generate an empty form.
$test = new TestRecord( $primaryKey );
[...]
$test->generateForm(); // Generate an edit form with actual $test values.
Static side of my class deals with blank/new logics,
while instance side means live data are used.
PHP 5.3 allows to achieve this by using __call, __callStatic and static:: :
public function __call( $name, $args )
{
if ( $name == 'generateForm' ) {
$this->fields = static::createFields(); // Action 1 : static.
$this->fillFields(); // Action 2 : instance.
static::renderForm( $this->fields ); // Action 3 : static.
}
}
public static function __callStatic( $name, $args )
{
if ( $name == 'generateForm' ) {
$fields = static::createFields(); // Action 1 : static.
// Action 2 : none.
static::renderForm( $fields ); // Action 3 : static.
}
}
Note: The static:: late binding qualifier is used because my 3 action methods (createFields, fillFields and rendreForm) are implemented as protected in the subclasses of this one, which is abstract. This is possible because PHP let protected members be accessed in both directions: from base to subclass, but from subclass to superclass as well. Which is different from other OO languages, as far as I know.
as in core php we use index.php?var=, so to do the same thing in oop php what should we use.

Categories