How to extend a php class with more classes - php

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.

Related

Extend PHP Class to allow new methods found via __callStatic

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

Initialize php object with arguments, while its constructor doesn't take any (singleton pattern)

I have only basic PHP knowledge and am reading the book "PHP 5 E-commerce Development.pdf" which code source can be found here: https://github.com/rogeriorps/books_demo.
I am right at the beginning, on the creation of the registry with "objects" such as database handling, authentication and template sending.
I have a problem with the last line of code of this function, in a class that is a singleton and has objects:
public function storeObject( $object, $key )
{
if( strpos( $object, 'database' ) !== false ) {
$object = str_replace( '.database', 'database', $object);
require_once('databaseobjects/' . $object
. '.database.class.php');
} else {
require_once('objects/' . $object . '.class.php');
}
self::$objects[ $key ] = new $object( self::$instance );
}
Well, for the authentication class for instance, the constructor is empty: public
function __construct() { }
So it would require authentication.class.php and then create a new authentification(self::$instance)... On a constructor has no arguments!
How is that possible? What bothers me is the use of the word new, which normally calls the empty constructor, and gives it arguments out of the blue.
Any further explanations about how this all works are welcome as well, thank you :-)
PHP is a quite forgiving language, in that certain language constructs and practices are not as strictly applied as in other programming languages.
PHP does not complain if you provide more parameters than a class method expects, whether that method is a costructor or regular method. See below, which outputs "Hello World!" just fine:
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
class Foo
{
public function __construct() {}
public function hello($input) { return 'Hello ' . $input . '!'; }
}
$foo = new Foo(123);
echo $foo->hello('World', 'Universe');
I'm still trying to understand what your question is.
While the link to the source code is good, can you be more specific as to what files you are talking about.
Is this the line you are referring too? :
//I will call this class 'Singleton',as I have no idea what it's name is.
class Singleton
{
protected static $instance;
protected static $objects = [];
...
public function storeObject( $object, $key )
{
...
self::$objects[ $key ] = new $object( self::$instance ); //<--- this line
...
}
}
And then you say you have a class like this ( with an empty constructor )
class authenticate{
public function __construct() {} //empty
}
IF I follow that right, the extra argument is ignored in this case. However, consider having another class that can be stored in Singleton
class user{
//instance of singleton
protected $singleton;
public function __construct( $Singleton ) {
$this->singleton = $Singleton;
}
}
So in this case the same class Singlton calls a different class that does accept an instance of Singlton.
This is what I would call a form of Polymorphism.
Polymorphism is the provision of a single interface to entities of different types.
Personally I would prefer they actually have an interface for this. I will try to explain this as best I can. An interface can be thought of like a contract, it guarantees that any class implementing it will expose some public methods, with given arguments, in a specific order.
For example this
interface StorableObjectInterface{
//interfaces do not contain a method body, only the definition
//here I am using type hinting to tell it to only accept instance of Singleton
public function __construct( Singleton $Singleton );
}
So what this does is require that every class that implements this interface requires an instance of singleton as it's constructors first argument. They can still ignore it, but it should be a contractual obligation of being called from Singleton (IMO).
Then your clasess would look like this
class authenticate implements StorableObjectInterface{
public function __construct(Singleton $Singleton) {} // still empty
}
class user implements StorableObjectInterface{
//instance of singlton
protected $singlton;
public function __construct(Singleton $Singleton ) {
$this->singlton = $Singleton;
}
}
And then to lock it all together in Singleton you would check that $Object implements the interface.
public function storeObject( $object, $key )
{
...
if( is_a( $object, StorableObjectInterface::class ){
self::$objects[ $key ] = new $object( self::$instance );
}else{
//throw exception
throw new Exception("Class {$object} must implement interface ".StorableObjectInterface::class);
}
...
}
This is the way I would do it... It wasn't clear if you are just Using someones system, or creating your own.
So you may wonder why go through all this trouble, I'll give you an example.
Say later on you may need something like a path to a config file in authenticate, so you can easily load you credentials etc.
So you look in that class and see this ( we'll forget we know what we know )
class authenticate{
public function __construct() {} // still empty
}
So you figure you can just tack it in the constructor ( say you were using this class somewhere outside of Singlton ). So you change it.
class authenticate{
protected $config;
public function __construct($configFile = null) {
if( $configFile )
$this->config = include $configFile;
}
}
//then you call it for you new code
$z = new authenticate('passwordsAndStuf.php');
This is all fine until Singleton calls that constructor with an instance of itself. Now everything blows up. The main issue is that just looking at authenticate there is no way to tell this is going to happen. So by adding an Interface we are making a contract with Singleton any class implementing this interface will always accept an instance ofSingleton` as the first argument.
Hope that makes sense.

Accessing a class member variable of type Array using __get or similar

Previously a class which I'm now rebuilding had a member variable $settings which was an array of settings, strangely enough.
class MyClass {
public $settings = array();
public function __construct() {
if( empty( $this->settings ) ) {
$this->settings = require( 'settings.php' ); // e.g. return array('setting1'=>4);
}
}
}
These settings were accessed by $object->settings['keyname'];
The means by which these keys are accessed has been moved into a method now. However, the application itself is riddled with calls to $object->settings['keyname']. I was wondering is there a way which I can catch any calls to the $settings member variable and return it using the new function.
I've looked at __get($name) but $name only contains settings rather than the array key which I need. What I'd need to pass would be the keyname to the my $object->get() method.
The reason I want to do this is so that I can trigger errors in a log file showing me where the deprecated calls to $object->settings[] are without breaking the application. Obviously setting $setting to private would give me lots of fatal errors and I could work through but there are multiple developers working on this codebase which I'd prefer not to break. If I could implement this as a temporary solution it'd help.
I realise there are repositories etc which we could use so that I could work on it separately and check it in afterwards but I'm looking for a quick, temporary solution as we're porting our codebase to Git soonish.
Totally possible:
<?php
class Logger {
public function log( $name, $backtrace ) {
/**
* $name is the name of the array index that was called on
* MyClass->settings( ). $backtrace contains an array in which
* you can find and determine which file has accessed that index,
* and on which line.
*/
$last = array_shift( $backtrace );
$message = sprintf( "Setting '%s' was called by file '%s' on line %d",
$name,
$last['file'],
$last['line']
);
echo $message;
}
}
class MyClass {
protected $settings;
public function __construct( ) {
$this->settings = new Settings( new Logger( ), array( 'foo' => 'bar' ) );
}
public function __get( $name ) {
if( $name === 'settings' ) {
return $this->settings;
}
}
}
class Settings extends ArrayObject {
protected $logger;
protected $settings;
public function __construct( Logger $logger, array $settings ) {
$this->logger = $logger;
parent::__construct( $settings );
}
public function offsetGet( $name ) {
$backtrace = debug_backtrace( );
$this->logger->log( $name, $backtrace );
return parent::offsetGet( $name );
}
}
$myClass = new MyClass( );
echo $myClass->settings['foo'] . "\n";
Sure, it's hackish, and you probably don't want to keep this in your codebase, but for logging deprecated uses, it might be extremely helpful. Just log for a set period of time, and then replace the $this->settings = new Settings( ) with $this->settings = array( ).
By the way, the output of that exact code is the following:
berry#berry-pc:~/Desktop% php foo.php
Setting 'foo' was called by file '/home/berry/Desktop/foo.php' on line 53
bar
To find where scripts SET a variable in settings:
I would suggest putting a tiny dirty hack in the constructor and the destructor of your MyClass class. In the constructor scan the $settings and check if a deprecated value exists (save it somewhere temp). In the destructor do the same check, and cross match the two results. When you have new variables during the destruction you know that the $_SERVER['PHP_SELF'] has set it.
To find where scripts access it:
Brutal, but I would recommend just converting all deprecated values to new ones, and watch things stop working. You'll spend less time tracking down what broke, and reading the "This used to work and doesn't" complaints, than you will modifying your class to use a logger

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);

Using log4php in a static context

I'm currently in the process of moving from our own proprietary logging solution to log4php.
We use a lot of classes with only static methods in our project. The documentation defines the basic use case like:
class MyClass {
private $logger;
public function __construct() {
$this->logger = Logger::getLogger(__CLASS__);
$this->logger->debug('currently in constructor');
}
}
But I can't use that, cause I need $logger to be available in a static context as well. Making $logger static as well doesn't help either, because the constructor for my class is never called (as all its members are static).
The documentation tells me to use a static initializer for that member then. But then I would have to remember to call that for all classes I use. And that seems too error-prone.
So I came up with this:
class Foo {
private static $logger = null;
private static function logger() {
if( null == self::$logger ) self::$logger = Logger::getLogger( __CLASS__ );
return self::$logger;
}
public static function bar() {
self::logger()->debug( "test" );
}
}
Foo::bar();
But that seems like too much overhead as well. So, any suggestions?
I came up with one solution that works quite well but requires $logger to be public.
class Foo {
public static $logger = null;
public static function bar() {
self::$logger->debug( "test" );
}
}
$loggerName = "logger";
// Iterate over all declared classes
$classes = get_declared_classes();
foreach( $classes as $class ) {
$reflection = new ReflectionClass( $class );
// If the class is internally defined by PHP or has no property called "logger", skip it.
if( $reflection->isInternal() || !$reflection->hasProperty( $loggerName ) ) continue;
// Get information regarding the "logger" property of this class.
$property = new ReflectionProperty( $class, $loggerName );
// If the "logger" property is not static or not public, then it is not the one we are interested in. Skip this class.
if( !$property->isStatic() || !$property->isPublic() ) continue;
// Initialize the logger for this class.
$reflection->setStaticPropertyValue( $loggerName, Logger::getLogger( $class ) );
}
This I only have to define the $logger property once per class and run my initialization code once (I guess after the require_once section of the entry point of my application).
The performance impact of that code is negligible, especially since it is only run once (compared to my initial solution). This is what I measured inside a VirtualBox VM on an Intel Core2 Q9450 #2.66GHz:
10000 iterations for 157 classes completed in 2.6794s. Average per iteration: 0.00026794s

Categories