Looking for a flexible way to allow other developers to extend render methods for a templating system, basically allowing them to generate their own render::whatever([ 'params' ]) methods.
Current set-up work well from a single developer point of view, I have a number of classes set-up based on context ( post, media, taxonomy etc ), with a __callStatic method collecting the calling function which checks if the method_exists within the class, and if so, extracts any passed arguments and renders the output.
quick example ( pseudo-code ):
-- view/page.php
render::title('<div>{{ title }}</div>');
-- app/render.php
class render {
public static function __callStatic( $function, $args ) {
// check if method exists
if ( method_exists( __CLASS__, $function ){
self::{ $function }( $args );
}
}
public static function title( $args ) {
// do something with the passed args...
}
}
I want to allow developers to extend the available methods from their own included class - so they could create for example render::date( $args ); and pass this to their logic to gather data, before rendering the results to the template.
The questions is, what approach would work best and be performant - errors are safety are not a big concern at this point, that can come later.
EDIT --
I am already making this work by doing the following ( pseudo-code again.. ):
-- app/render.php
class render {
public static function __callStatic( $function, $args ) {
// check if method exists
if (
method_exists( __CLASS__, $function
){
self::{ $function }( $args );
}
// check if method exists in extended class
if (
method_exists( __CLASS__.'_extend', $function
){
__CLASS__.'_extend'::{ $function }( $args );
}
}
public static function title( $args ) {
// do something with the passed args...
}
}
-- child_app/render_extend.php
class render_extend {
public static function date( $args = null ) {
// do some dating..
}
}
The issue here is that this is limited to one extension of the base render() class.
A common way (used by Twig and Smarty, for a couple of examples), is to require developers to manually register their extensions as callables. The render class keeps a record of them, and then as well as checking its own internal methods, also checks this list from _callStatic.
Based on what you have already, this might look like this:
class render
{
/** #var array */
private static $extensions;
public static function __callStatic($function, $args)
{
// check if method exists in class methods...
if ( method_exists( __CLASS__, $function )) {
self::{$function}(self::$args);
}
// and also in registry
elseif (isset(self::$extensions[$function])) {
(self::$extensions[$function])($args);
}
}
public static function title($args)
{
// do something with the passed args...
}
public static function register(string $name, callable $callback)
{
self::$extensions[$name] = $callback;
}
}
A developer would make use of this like so:
render::register('date', function($args) {
// Do something to do with dates
});
Full demo here: https://3v4l.org/oOiN6
Related
I'm learning to code Wordpress Multisite the OOP way, and since I'm pretty new to OOP now I am in a situation that I can't seem to solve on my own.
Specifically, I'm creating some classes to create admin pages (both at the network and subsite level) with an OOP approach. Here's my simplified code:
class AdminPage {
public function __construct( $args ) {
add_action( 'admin_menu', array( $this, 'add_admin_page' ) );
}
public function add_admin_page() {
add_menu_page( // arguments );
}
}
class AdminNetworkPage extends AdminPage {
public function __construct( $args ) {
add_action( 'network_admin_menu', array( $this, 'add_admin_page' ) );
}
}
The code works, but as you can see I have to extend the AdminPage class with the sole purpose of changing the hook in the constructor ( I need admin_network_menu for admin pages at network level instead of admin_menu).
Is there a better way to do this? A way to have one class, put both hooks in the constructor of that class and then selectively call either one or the other?
Unfortunately, creating a new instance of the class ($page = new AdminPage) and then calling a method on it (e.g. $page->add_admin_page() ) won't work in this case, because then I get an error saying add_menu_page is undefined... It all has to happen in the constructor.
You could do something like this:
class AdminPage {
public function __construct( $args, $networkPage = false ) {
if($networkPage) {
add_action( 'network_admin_menu', array( $this, 'add_admin_page' ) );
} else {
add_action( 'admin_menu', array( $this, 'add_admin_page' ) );
}
}
public function add_admin_page() {
add_menu_page( // arguments );
}
}
And then pass a second parameter to the class to change which action you add, e.g.
$page = new AdminPage($args);
$networkPage = new AdminPage($args, true);
Whether or not this is better than just extending the class is debatable; I personally think there's nothing wrong with your initial setup, the point of OOP isn't to create as few classes as possible - if your code make more sense as two separate classes (with one extending the other) then there's nothing wrong with that.
I have part of abstract base class that looks like this:
abstract class Fragment_Cache {
static $in_callback = false;
abstract public function callback( $name, $args );
}
I need to flip $in_callback to true during callback() method execution. That is used in other part of code to prevent nesting of caches (so I want to cache widget and menu, but not menu inside widget).
However since it is abstract method I cannot rely on subclass implementations for that.
I also cannot flip flag on/off around method call, because it is passed on and executed by different library that takes care of running it async instead of during page load.
How can I architecture myself out of this corner? :)
Actual monstrosity that sets up its call:
$output = tlc_transient( 'fragment-cache-' . $this->type . '-' . $name . $salt )
->updates_with( array( $this, 'callback' ), array( $name, $args ) )
->expires_in( $this->timeout )->get();
How about hiding the actual functionality in another function?
abstract class Fragment_Cache {
static $in_callback = false;
public function callback($name, $args)
{
self::$in_callback = true;
$this->doCallback($name, $args);
self::$in_callback = false;
}
abstract protected function doCallback( $name, $args );
}
You need to overwrite doCallback, which will do the actual heavy-lifting, but is not accessible directly from the outside, only via callback.
abstract class Fragment_Cache {
static $in_callback = false;
public function callback( $name, $args ){
self::$in_callback = true;
$ret = $this->__doCallback($name, $args);
self::$in_callback = false;
return $ret;
}
abstract protected function __doCallback($name, $args);
}
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);
I was wondering if it was a way to get what you would see as
class main_class extends main_class {...}
But php was not happy. :(
So then I though to myself lets ask stackoverflow, I'm sure someone will know a solution.
Never the less several hours of debugging I self-solved my problem, with only a little code.
The problem was the fact of class some_class won't let you override an existing class so what I needed to do was use __get and __call and add another 2 lines into my __construct function.
So here is my solved-code:
class main_class {
private $_MODS = array(),...;
public ...;
public function __construct(...) {
...
global $MODS_ENABLED;
$this -> $_MODS = $MODS_ENABLED;
}
...
public function __get( $var ) {
foreach ( $this->_MODS as $mod )
if ( property_exists( $mod, $var ) )
return $mod -> $var;
}
public function __call( $method, $args ) {
foreach ( $this->_MODS as $mod )
if ( method_exists( $mod, $method ) )
return call_user_method_array( $method, $mod, $args );
}
}
Then simply run this to extend my main_class without overriding the original functions, so it has me run my new functions but if I need to I can get the original functions:
$MODS_ENABLED=array();
class mod_mail {...}
$MODS_ENABLED[]=new mod_mail;
Now lets load our class and run a function from our mod:
$obj = new main_class(...);
$obj -> mail("root#localhost", "me#me.me", "Testing a mod.", "This email was sent via My main_class but this is a mod that extended main_class without renaming it.");
Okay well my mod was not for sending emails but instead redirects sub-domains to there aliased pathname, but you understand the concept shown here.
Edit: After I solved the issue I saw a comment saying a possible duplicate exists so I check it out and find out someone else has an extremely similar solution, but please don't mark it as a duplicate as he was asking about adding to a class that was already constructed, I want to override functions while constructing. My solution takes in an array of constructed classes and "merges" them into my main_class, This method does reserve the original functions but I can also call the original functions using another function to by-pass the __call function.
Thanks to anyone who posted answers.
In C# you would do this by defining a partial class. It's basically like writing more of the same class in a different file. I'm not sure if PHP supports this, but this article may help?
http://www.toosweettobesour.com/2008/05/01/partial-classes-in-php/
Self-Solved :)
I have figured this out on my own and I'm sure someone else will find it useful so here is my php code.
class main_class {
private $_MODS = array(),...;
public ...;
public function __construct(...) {
...
global $MODS_ENABLED;
$this -> $_MODS = $MODS_ENABLED;
}
...
public function __get( $var ) {
foreach ( $this->_MODS as $mod )
if ( property_exists( $mod, $var ) )
return $mod -> $var;
}
public function __call( $method, $args ) {
foreach ( $this->_MODS as $mod )
if ( method_exists( $mod, $method ) )
return call_user_method_array( $method, $mod, $args );
}
}
Then simply run:
$MODS_ENABLED=array();
class mod_mail {...}
$MODS_ENABLED[]=new mod_mail;
Now all you need to do is just call your main class via.
$obj = new main_class(...);
$obj -> mail("root#localhost", "me#me.me", "Testing a mod.", "This email was sent via my main_class but this is a mod that extended main_class without renaming it.");
Okay this was not my usage but I'm sure you get the idea, My class is a CMS-back-end and my mod was a redirecting a few sub-domains to other locations, such as mail.sitegen.com.au and phpmyadmin.sitegen.com.au get redirected to there web-gui's out side of my CMS. I am also making more mods for my CMS.
BTW: my extensions are split into two categories, mods and plugins, mods run on every site that is powered by my CMS (SiteGen) and plug-ins have there own settings for each site and they are not required. In other words mods are chosen by me, and plug-ins are chosen by the owner of the site.
I'm working with a CMS, Joomla, and there's a core class which renders a set of parameters to a form, JParameter. Basically it has a render() function which outputs some table-laden HTML which is not consistent with the rest of my site.
For issues of maintainability, and because I have no idea where else this is being used, I don't want to change the core code. What would be ideal would to be able to define a new class which extends JParameter and then cast my $params object down to this new sub class.
// existing code --------------------
class JParameter {
function render() {
// return HTML with tables
}
// of course, there's a lot more functions here
}
// my magical class -----------------
class MyParameter extends JParameter {
function render() {
// return HTML which doesn't suck
}
}
// my code --------------------------
$this->params->render(); // returns tables
$this->params = (MyParameter) $this->params; // miracle occurs here?
$this->params->render(); // returns nice html
There's always PECL's Classkit but I get a feeling that you'd really rather not do this. Assuming you're directly calling $this->params->render(), you might just want to make a function/object that does an alternate rendering ( MyParamRenderer::render($this->params)) and avoid performing OO gymnastics not natively supported by the language.
What about creating a decorator of sorts that delegates anything apart from JParameter::render() to the existing object
class MyJParameter {
private $jparm;
function __construct( JParameter $jparm ) {
$this->jparm = $jparm;
}
function render() {
/* your code here */
}
function __get( $var ) {
if( isset( $this->$jparm->$var ) {
return $this->$jparm->$var;
}
return false;
}
function __set( $var, $val ) {
/* similar to __get */
}
function __call( $method, $arguments ) {
if( method_exists( $this->jparm, $method ) {
return call_user_func_array( array( $this->jparm, $method ), $arguments );
}
return false;
}
}
Or is this just too smelly?