PHP function inside another function [ class ] - php

I have the following code, perform a global function within a class to fill the functions of wordpress, the problem is that the only way that I could get a variable public class is as follows
class Core {
public $notice;
function __construct(){
$this->core_function();
}
function core_function(){
global $globalvar;
$globalvar = $this;
function notice_global(){
global $globalvar;
return $globalvar->notice;
}
}
function set_notice(){
$this->notice = array('Warning');
}
}
$GP = new Core();
$GP->set_notice();
var_dump(notice_global());
Any other ideas or suggestions, this code is correct or not?

As you said in the comments, you need global function due to wordpress hook method (for a plugin, I suppose).
This is not necessary: there is a way to pass an object method (not a whole object) to wordpress.
You can try in this way:
class Core {
public $notice;
function get_notice()
{ return $this->notice; }
function set_notice()
{ $this->notice = array('Warning'); }
}
$GP = new Core();
$GP->set_notice();
add_action( 'save_post', array( $GP, 'get_notice' ) );
Or - for a better flexibility - in this way:
class Core {
public $notice;
function get_notice()
{ return $this->notice; }
function set_notice()
{ $this->notice = array('Warning'); }
function add_wp_action( $hook, $method )
{ add_action( $hook, array( $this, $method ) ); }
}
$GP = new Core();
$GP->set_notice();
$GP->add_wp_action( 'save_post', 'get_notice' );
By this way, you can directly set all your wp hooks in the class and call they directly with an object method, without using globals variables or function tricks.

I'm not sure if I'm understanding you right, but notice_global can be moved out of that class.
Globals have scope outside of classes

There is no need for these functions. You've defined $notice as public property. You can access it like this: $GP->notice;
You might also want to read documentation on visibility of methods and properties.

Related

PHPUnit mocking WordPress function calls

I am building a simple WordPress plugin and I am looking to add unit tests using PHPUnit, I have the following class code:
<?php
namespace App;
class MyPlugin {
public function __construct()
{
add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']);
add_action('admin_menu', [$this, 'createAdminMenu']);
add_action('admin_init', [$this, 'settings']);
}
public function enqueueAdminScripts()
{
// Code here
}
public function createAdminMenu()
{
// Code here
}
public function settings()
{
// Code here
}
}
And this is my test file:
<?php
use App\MyPlugin;
use PHPUnit\Framework\TestCase;
class MyPlugin extends TestCase {
protected $myPlugin;
protected function setUp(): void
{
$this->myPlugin = new MyPlugin();
}
public function testRegister()
{
$this->assertInstanceOf('\App\MyPlugin', $this->myPlugin);
}
}
However I am always getting the error Error: Call to undefined function App\add_action()
What is the best way of mocking the add_action calls purely within PHPUnit without having to rely on a third party solution?
Any help with this would be greatly appreciated.
Below is a very simple mock of the hook system. It doesn't take into account priorities, and it is more modernized for expected usage so it might not support some previous edge cases.
You'll notice that add_action actually calls the exact same code as add_filter, and that's what core actually does.
The gist is that there's a global array. You could store this in your base class if you wanted, too. WordPress backs their stuff with objects, too, but I'm simplifying things here.
global $wp_test_hooks;
if(!is_array($wp_test_hooks)) {
$wp_test_hooks = [];
}
if(!function_exists('add_filter')) {
function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
global $wp_test_hooks;
$wp_test_hooks[$hook_name][] = [
$callback,
$priority,
$accepted_args,
];
return true;
}
}
if(!function_exists('add_action')) {
function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
return add_filter( $hook_name, $callback, $priority, $accepted_args );
}
}
if(!function_exists('apply_filters')){
function apply_filters( $hook_name, ...$values ) {
$value = array_shift($values);
global $wp_test_hooks;
if(array_key_exists($hook_name, $wp_test_hooks)){
foreach($wp_test_hooks[$hook_name] as $parts){
list($callback, $priority, $accepted_args) = $parts;
$value = $callback($value, ...$values);
}
}
return $value;
}
}
if(!function_exists('do_action')){
function do_action( $hook_name, ...$arg ) {
global $wp_test_hooks;
if(array_key_exists($hook_name, $wp_test_hooks)){
foreach($wp_test_hooks[$hook_name] as $parts){
list($callback, $priority, $accepted_args) = $parts;
$callback(...$arg);
}
}
}
}
You can see it in action by using normal WordPress code:
add_action('name1', static function(){echo 'In the Action';});
add_filter('name2', static function($originalValue){return 'In the Filter';});
do_action('name1', 'test', 'stuff');
echo PHP_EOL;
echo apply_filters('name2', 'test', 'stuff');
Demo here: https://3v4l.org/DAVl0
All that said, if you don't manually call do_action or apply_filters in your code, don't both with the bulk of this. You can use the functions, just leave their bodies empty. More often than not, it is WordPress or a plugin that is invoking those methods, and if you aren't invoking WordPress or calling those functions, there's no need for logic or even the global array. What you probably want is:
if(!function_exists('add_filter')) {
function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) { }
}
if(!function_exists('add_action')) {
function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) { }
}
if(!function_exists('apply_filters')){
function apply_filters( $hook_name, ...$values ) { }
}
if(!function_exists('do_action')){
function do_action( $hook_name, ...$arg ) { }
}
I'd say this depends a lot what you'd actually want to test (and therefore call).
Reading your question you would like to put Wordpress itself out of the equation to essentially test your code.
That is testing a plugin not as an integration test.
Now Wordpress does not make it exactly easy to test but you could try to get as far as possible. I'd say the issue you describe in the question lays in the constructor:
public function __construct()
{
add_action('admin_enqueue_scripts', [$this, 'enqueueAdminScripts']);
add_action('admin_menu', [$this, 'createAdminMenu']);
add_action('admin_init', [$this, 'settings']);
}
This constructor only works within Wordpress. But now you want to test it without. Well, easy:
public function __construct()
{
}
Problem sovled. Now you'd say, this is overly clever, right? The plugin needs to be initialized. True:
public static function setup(): MyPlugin
{
$plugin = new self();
add_action('admin_enqueue_scripts', [$plugin, 'enqueueAdminScripts']);
add_action('admin_menu', [$plugin, 'createAdminMenu']);
add_action('admin_init', [$plugin, 'settings']);
return $plugin;
}
With this, you still have one central way to setup your plugin while you're able in your unit-tests to test calling the concrete functions. The new operator (in your test) and the way you've written the constructor does not stand in your way any longer and the code became testable.
If those as well depend on Wordpress functions, you'll run into the same problem thought. However the functionality you actually may want to test here might not entirely, so extracting the code that does not into methods of its own, allow actual unit-tests of those independent methods (those that have no side-effects on the global scope which Wordpress uses).
And I'd say your nose is good here: At the end of the day you want as many of these pure methods (without side-effects) and less those that depend on global state and Wordpress as a dependency.
You could do other tests (e.g. integrating with Wordpress) with the Wordpress base test case class to not reinvent the wheel. To only do that for certain methods, group your tests in different directories, e.g. test/unit and test/wordpress.
Then you can decide what kind of test you want to write.

How To Pass Arguments To new Class & Define Variables

I'm trying to modify the plugin function in the original PHP class named My_Widget_Admin which i have copied over from a plugin to my theme, but get Fatal error Too few arguments to function My_Widget_Admin
Here is the code i added in my theme :
class Custom_Admin extends My_Widget_Admin {
function item_select() {
// Code
}
}
$new = new Custom_Admin;
$new->item_select();
I think it has something to do with this code from the plugin :
private $_instance;
private $_widget_object;
function __construct( $instance, $widget_object ) {
$this->_instance = $instance;
$this->_widget_object = $widget_object;
$this->form();
}
I need to pass these 2 arguments $instance, $widget_object to the new function Custom_Admin.
How do i do that?
Passing arguments to a constructor must be done when the class gets instantiated. This is done with the new keyword.
class Custom_Admin extends My_Widget_Admin {
function item_select() {
// Code
}
}
$new = new Custom_Admin($instance, $widget_object);
$new->item_select();

Passing argument to a method in callback array in add_action() - WordPress

I am trying to pass an argument (say $arg) to the second parameter/callback of add_action() using do_action() in Wordpress.
do_action( 'my_hook', $arg);
the 'my_hook' in do_action() will refer to the below add_action()
class My_Class {
public function __construct () {
add_action( 'my_hook', array( $this, 'my_method ($arg)' ) );
}
public function my_method ($arg) {
some_function ($arg);
// some code here....
}
}
Note: I am not trying this in fuctions.php. Trying to modify core files. I know modifying core files is not recommended. But, I want to explore the horizons (I will return back to the home though)
Please provide a way to pass the argument into my_method(). Thanks in advance.
Hm I think this should work if you take out the ($arg) from the add_action call.
class My_Class {
public function __construct () {
add_action( 'my_hook', array( $this, 'my_method' ) );
}
public function my_method ($arg) {
some_function ($arg);
// some code here....
}
}
// make sure you are creating a new object so __construct() is called
$instance = new My_Class();
do_action( 'my_hook', $arg );
If you look at the documentation, add_action lets you specify the number of arguments an action accepts. It defaults to 1.
https://developer.wordpress.org/reference/functions/add_action/#more-information

Variable in global variable: "global variable{variable}". It works, but is it OK?

TL;DR: Say I have a global instantiated class
$GLOBALS['my_plugin'] = instance();
and I want to use it somewhere else, but I dont know the name 'my_plugin'. Actually I do of course, but my helper files don't. In order to So I use;
$plugin_name = 'my_plugin';
global $plugin_name, ${$plugin_name};
And it works I can access $my_plugin variable and use the object just fine. What I am doing is basically making use of "variable variables",but it somehow feels weird. Is it OK ? Or is there a better way to tackle this.
Long Version
// contents of plugin.php
class myplugin_class {
$_instance = null;
public $plugin_name = 'my_plugin';
public function __construct() {
$this->init();
}
public static function instance() {
if ( is_null( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
function init() {
$this->assign_global();
$this->include_helper();
}
function assign_global() {
global $plugin_slug;
$plugin_slug = $this->plugin_name;
}
function include_helper() {
include_once('helper.php' );
}
}//end class
function plug_inst() {
return myplugin_class::instance();
}
$GLOBALS['my_plugin'] = plug_inst();
Helper.php
// contents of helper.php
class helper_class {
public function __construct() {
$this->some_function_that_needs_my_plugin_object();
}
function some_function_that_needs_my_plugin_object() {
global $plugin_slug, ${$plugin_slug};
print_r(${$plugin_slug});
//It gives me the $my_plugin object I want. It's all good, it works. But is it OK?
}
}
Upon deeper, I mean deeper search; I found out that a.) Even if it works it is not OK to use variable variables in globals. b.) It should be OK to retrive it with a function.

How can I dereference a constructor?

Ideally I would like to do something like this....
$formElement->addValidator
(
(new RegexValidator('/[a-z]/') )->setErrorMessage('Error')
// setErrorMessage() returns $this
);
Of course PHP won't allow that, so I settle for this...
$formElement->addValidator
(
RegexValidator::create('/[a-z]/')->setErrorMessage('Error')
);
And the code in the Base class....
static public function create( $value )
{
return new static( $value );
}
I would like to go one step further and do something like this...
static public function create()
{
return call_user_func_array( 'static::__construct', func_get_args() );
}
Again, PHP won't allow me to do this. I could code individual 'create' methods for each validator, but I want it to be a little more slick.
Any suggestions please?
Corzin massively pointed me in the right direction, Reflection - (thanks Krzysztof).
Please note that late static binding applies, which is only a feature of PHP >= 5.3
The method of interest is Validator::create(). It provides a work-around for the lack of ability to call methods on objects which have bee created inline (see my original question).
The base class...
class Validator
{
....
static public function create()
{
$class = new ReflectionClass( get_called_class() );
return $class->newInstanceArgs( func_get_args() );
}
public function setErrorMessage( $message )
{
....
}
The extended class....
class RegexValidator extends Validator
{
public function __construct( $regex )
{
....
}
A usage example...
$form
->getElement('slug')
->setLabel( 'Slug' )
->addValidator( RegexValidator::create('/[a-z]/')->setErrorMessage('Error') )
->addValidator( RequiredValidator::create() );
Use ReflectionClass::newInstanceArgs from Reflection API:
$class = new ReflectionClass(__CLASS__);
return $class->newInstanceArgs($args);

Categories