PHP magic constants - automatically passed to function? - php

I wrote a simple access control system which reads an array of access strings, and return true or false depending on the result.
I would call it as follows (for example in a method list_user_data of class User): `
if (current_user_can(__CLASS__, __METHOD__)) {
...
}
and inside there, it checks if the current user has permission to access method list_user_data in class User.
It works, but I find it annoying that I always have to specify __CLASS__ and __METHOD__ in the call. Is there a way to get those values from within the current_user_can function for the calling function so that I can simply call current_user_can() without having to pass the magic constants?
My code works as is, but I am thinking it could be improved.
Is this possible?

The return value from debug_backtrace should return the calling function in the 2nd entry (index 1), for example:
<?php
function current_user_can()
{
$backtrace = debug_backtrace(false, 2);
// ToDo: Check if $backtrace[1] (and especially the class-key of that) actually exist...
// It should always (although the class key might not if this function isn't called from within a class), since this function is being called
// but it's still a good habbit to check array keys before accessing the array
$callerClass = $backtrace[1]["class"];
$callerMethod = $backtrace[1]["function"];
// ToDo: implementation of check, in this example $callerClass would be "User" and $callerMethod would be "list_user_data"
return true;
}
class User {
public function list_user_data() {
if (current_user_can())
{
}
}
}
$user = new User();
$user->list_user_data();

Related

How to store a name of a function in an array and call it when needed? [duplicate]

I need to be able to call a function, but the function name is stored in a variable, is this possible? e.g:
function foo ()
{
//code here
}
function bar ()
{
//code here
}
$functionName = "foo";
// I need to call the function based on what is $functionName
$functionName() or call_user_func($functionName)
My favorite version is the inline version:
${"variableName"} = 12;
$className->{"propertyName"};
$className->{"methodName"}();
StaticClass::${"propertyName"};
StaticClass::{"methodName"}();
You can place variables or expressions inside the brackets too!
Solution: Use PHP7
Note: For a summarized version, see TL;DR at the end of the answer.
Old Methods
Update: One of the old methods explained here has been removed. Refer to other answers for explanation on other methods, it isn't covered here. By the way, if this answer doesn't help you, you should return upgrading your stuff. PHP 5.6 support has ended in January 2019 (now even PHP 7.2 and 7.3 are not being supported). See supported versions for more information.
As others mentioned, in PHP5 (and also in newer versions like PHP7) we could use variables as function names, use call_user_func() and call_user_func_array(), etc.
New Methods
As of PHP7, there are new ways introduced:
Note: Everything inside <something> brackets means one or more expressions to form something, e.g. <function_name> means expressions forming a function name.
Dynamic Function Call: Function Name On-the-fly
We can form a function name inside parentheses in just one go:
(<function_name>)(arguments);
For example:
function something(): string
{
return "something";
}
$bar = "some_thing";
(str_replace("_", "", $bar))(); // something
// Possible, too; but generally, not recommended, because makes your
// code more complicated
(str_replace("_", "", $bar))()();
Note: Although removing the parentheses around str_replace() is not an error, putting parentheses makes code more readable. However, you cannot do that sometimes, e.g. while using . operator. To be consistent, I recommend you to put the parentheses always.
Dynamic Function Call: Callable Property
A useful example would be in the context of objects: If you have stored a callable in a property, you have to call it this way:
($object->{<property_name>})();
As a simple example:
// Suppose we're in a class method context
($this->eventHandler)();
Obviously, calling it as $this->eventHandler() is plain wrong: By that you mean calling a method named eventHandler.
Dynamic Method Call: Method Name On-the-fly
Just like dynamic function calls, we can do the same way with method calls, surrounded by curly braces instead of parentheses (for extra forms, navigate to TL;DR section):
$object->{<method_name>}(arguments);
$object::{<method_name>}(arguments);
See it in an example:
class Foo
{
public function another(): string
{
return "something";
}
}
$bar = "another thing";
(new Something())->{explode(" ", $bar)[0]}(); // something
Dynamic Method Call: The Array Syntax
A more elegant way added in PHP7 is the following:
[<object>, <method_name>](arguments);
[<class_name>, <method_name>](arguments); // Static calls only
As an example:
class Foo
{
public function nonStaticCall()
{
echo "Non-static call";
}
public static function staticCall()
{
echo "Static call";
}
}
$x = new X();
[$x, "non" . "StaticCall"](); // Non-static call
[$x, "static" . "Call"](); // Static call
Note: The benefit of using this method over the previous one is that, you don't care about the call type (i.e. whether it's static or not).
Note: If you care about performance (and micro-optimizations), don't use this method. As I tested, this method is really slower than other methods (more than 10 times).
Extra Example: Using Anonymous Classes
Making things a bit complicated, you could use a combination of anonymous classes and the features above:
$bar = "SomeThing";
echo (new class {
public function something()
{
return 512;
}
})->{strtolower($bar)}(); // 512
TL;DR (Conclusion)
Generally, in PHP7, using the following forms are all possible:
// Everything inside `<something>` brackets means one or more expressions
// to form something
// Dynamic function call via function name
(<function_name>)(arguments);
// Dynamic function call on a callable property
($object->{<property_name>})(arguments);
// Dynamic method call on an object
$object->{<method_name>}(arguments);
$object::{<method_name>}(arguments);
// Dynamic method call on a dynamically-generated object
(<object>)->{<method_name>}(arguments);
(<object>)::{<method_name>}(arguments);
// Dynamic method call, statically
ClassName::{<method_name>}(arguments);
(<class_name>)::{<method_name>}(arguments);
// Dynamic method call, array-like (no different between static
// and non-static calls
[<object>, <method_name>](arguments);
// Dynamic method call, array-like, statically
[<class_name>, <method_name>](arguments);
Special thanks to this PHP talk.
Yes, it is possible:
function foo($msg) {
echo $msg."<br />";
}
$var1 = "foo";
$var1("testing 1,2,3");
Source: http://www.onlamp.com/pub/a/php/2001/05/17/php_foundations.html?page=2
As already mentioned, there are a few ways to achieve this with possibly the safest method being call_user_func() or if you must you can also go down the route of $function_name(). It is possible to pass arguments using both of these methods as so
$function_name = 'foobar';
$function_name(arg1, arg2);
call_user_func_array($function_name, array(arg1, arg2));
If the function you are calling belongs to an object you can still use either of these
$object->$function_name(arg1, arg2);
call_user_func_array(array($object, $function_name), array(arg1, arg2));
However if you are going to use the $function_name() method it may be a good idea to test for the existence of the function if the name is in any way dynamic
if(method_exists($object, $function_name))
{
$object->$function_name(arg1, arg2);
}
A few years late, but this is the best manner now imho:
$x = (new ReflectionFunction("foo"))->getClosure();
$x();
In case someone else is brought here by google because they were trying to use a variable for a method within a class, the below is a code sample which will actually work. None of the above worked for my situation. The key difference is the & in the declaration of $c = & new... and &$c being passed in call_user_func.
My specific case is when implementing someone's code having to do with colors and two member methods lighten() and darken() from the csscolor.php class. For whatever reason, I wanted to have the same code be able to call lighten or darken rather than select it out with logic. This may be the result of my stubbornness to not just use if-else or to change the code calling this method.
$lightdark="lighten"; // or optionally can be darken
$color="fcc"; // a hex color
$percent=0.15;
include_once("csscolor.php");
$c = & new CSS_Color($color);
$rtn=call_user_func( array(&$c,$lightdark),$color,$percent);
Note that trying anything with $c->{...} didn't work. Upon perusing the reader-contributed content at the bottom of php.net's page on call_user_func, I was able to piece together the above. Also, note that $params as an array didn't work for me:
// This doesn't work:
$params=Array($color,$percent);
$rtn=call_user_func( array(&$c,$lightdark),$params);
This above attempt would give a warning about the method expecting a 2nd argument (percent).
For the sake of completeness, you can also use eval():
$functionName = "foo()";
eval($functionName);
However, call_user_func() is the proper way.
Dynamic function names and namespaces
Just to add a point about dynamic function names when using namespaces.
If you're using namespaces, the following won't work except if your function is in the global namespace:
namespace greetings;
function hello()
{
// do something
}
$myvar = "hello";
$myvar(); // interpreted as "\hello();"
What to do?
You have to use call_user_func() instead:
// if hello() is in the current namespace
call_user_func(__NAMESPACE__.'\\'.$myvar);
// if hello() is in another namespace
call_user_func('mynamespace\\'.$myvar);
Complementing the answer of #Chris K if you want to call an object's method, you can call it using a single variable with the help of a closure:
function get_method($object, $method){
return function() use($object, $method){
$args = func_get_args();
return call_user_func_array(array($object, $method), $args);
};
}
class test{
function echo_this($text){
echo $text;
}
}
$test = new test();
$echo = get_method($test, 'echo_this');
$echo('Hello'); //Output is "Hello"
I posted another example here
Use the call_user_func function.
What I learnt from this question and the answers. Thanks all!
Let say I have these variables and functions:
$functionName1 = "sayHello";
$functionName2 = "sayHelloTo";
$functionName3 = "saySomethingTo";
$friend = "John";
$datas = array(
"something"=>"how are you?",
"to"=>"Sarah"
);
function sayHello()
{
echo "Hello!";
}
function sayHelloTo($to)
{
echo "Dear $to, hello!";
}
function saySomethingTo($something, $to)
{
echo "Dear $to, $something";
}
To call function without arguments
// Calling sayHello()
call_user_func($functionName1);
Hello!
To call function with 1 argument
// Calling sayHelloTo("John")
call_user_func($functionName2, $friend);
Dear John, hello!
To call function with 1 or more arguments
This will be useful if you are dynamically calling your functions and each function have different number of arguments. This is my case that I have been looking for (and solved). call_user_func_array is the key
// You can add your arguments
// 1. statically by hard-code,
$arguments[0] = "how are you?"; // my $something
$arguments[1] = "Sarah"; // my $to
// 2. OR dynamically using foreach
$arguments = NULL;
foreach($datas as $data)
{
$arguments[] = $data;
}
// Calling saySomethingTo("how are you?", "Sarah")
call_user_func_array($functionName3, $arguments);
Dear Sarah, how are you?
Yay bye!
If you were in a object context trying to call a function dynamically please try something like this code bellow:
$this->{$variable}();
Following code can help to write dynamic function in PHP.
now the function name can be dynamically change by variable '$current_page'.
$current_page = 'home_page';
$function = #${$current_page . '_page_versions'};
$function = function() {
echo 'current page';
};
$function();
The easiest way to call a function safely using the name stored in a variable is,
//I want to call method deploy that is stored in functionname
$functionname = 'deploy';
$retVal = {$functionname}('parameters');
I have used like below to create migration tables in Laravel dynamically,
foreach(App\Test::$columns as $name => $column){
$table->{$column[0]}($name);
}
Considering some of the excellent answers given here, sometimes you need to be precise.
For example.
if a function has a return value eg (boolean,array,string,int,float
e.t.c).
if the function has no return value check
if the function exists
Let's look at its credit to some of the answers given.
Class Cars{
function carMake(){
return 'Toyota';
}
function carMakeYear(){
return 2020;
}
function estimatedPriceInDollar{
return 1500.89;
}
function colorList(){
return array("Black","Gold","Silver","Blue");
}
function carUsage(){
return array("Private","Commercial","Government");
}
function getCar(){
echo "Toyota Venza 2020 model private estimated price is 1500 USD";
}
}
We want to check if method exists and call it dynamically.
$method = "color List";
$class = new Cars();
//If the function have return value;
$arrayColor = method_exists($class, str_replace(' ', "", $method)) ? call_user_func(array($this, $obj)) : [];
//If the function have no return value e.g echo,die,print e.t.c
$method = "get Car";
if(method_exists($class, str_replace(' ', "", $method))){
call_user_func(array($class, $method))
}
Thanks
One unconventional approach, that came to my mind is, unless you are generating the whole code through some super ultra autonomous AI which writes itself, there are high chances that the functions which you want to "dynamically" call, are already defined in your code base. So why not just check for the string and do the infamous ifelse dance to summon the ...you get my point.
eg.
if($functionName == 'foo'){
foo();
} else if($functionName == 'bar'){
bar();
}
Even switch-case can be used if you don't like the bland taste of ifelse ladder.
I understand that there are cases where the "dynamically calling the function" would be an absolute necessity (Like some recursive logic which modifies itself). But most of the everyday trivial use-cases can just be dodged.
It weeds out a lot of uncertainty from your application, while giving you a chance to execute a fallback function if the string doesn't match any of the available functions' definition. IMHO.
I dont know why u have to use that, doesnt sound so good to me at all, but if there are only a small amount of functions, you could use a if/elseif construct.
I dont know if a direct solution is possible.
something like
$foo = "bar";
$test = "foo";
echo $$test;
should return bar, you can try around but i dont think this will work for functions

Is it possible to Delete a value passed to a method, from within the method

Say I have a method (which in this particular case is a static method), and this method works on a given value. Once completed is there a way that I can automatically in the code, delete the variable (rather than the function copy).
I suspect from all I've read that this is not possible, but there are no clear declarations of such that my searching has found.
An example case:
Static Method:
public static function checkKey($keyValue = null)
{
if(!is_null($keyValue) && !empty($keyValue)) {
if($_SESSION['keyValue'] == $keyValue) {
unset($keyValue,$_SESSION['keyValue']);
return true;
}
unset($keyValue,$_SESSION['keyValue']);
return false;
}
return false;
}
Usage:
$valueToBeChecked = "I want this value unset from within the function"
//PHP page code
AbstractClass::checkKey($valueToBeChecked);
Is there a way that the method checkKey above can delete the value of $valueToBeChecked from within the method checkKey?
The fact it's a static method shouldn't be too critical, it's more the shape of is there a way that the function can delete a value that is set outside the funtion/method, when passed the variable as a parameter?
I realise this is possible if the whole thing is wrapped in a Class and the variable is saved as a class level variable (unset($this->var)), but I'm curious if there's any ability to "reach" variables from outside the scope such as
public static function checkKey($keyValue = null)
{
unset(\$keyValue);
}
I only have limited experience with namespacing but that's my best guess as to if this is possible, how to go about it.
simplified equiviliant outcome:
What I'm trying to reach is this action, entirely within the method:
$valueToBeChecked = "something"
AbstractClass::checkKey($valueToBeChecked);
unset($valueToBeChecked);
You cannot unset a variable from within a function and have that effect propagate. Per the manual:
If a variable that is PASSED BY REFERENCE is unset() inside of a function, only the local variable is destroyed. The variable in the calling environment will retain the same value as before unset() was called.
However, you can get equivalent behavior through pass-by-reference and setting to null:
function kill(&$value) {
$value = null;
}
var_dump($x); // NULL
$x = 'foo';
var_dump($x); // 'foo'
kill($x);
var_dump($x); // NULL
This works because, in PHP, there's no distinction made between a symbol that doesn't exist and a symbol that exists with a NULL value.

PHP combine $this variable

How to combine two variables to obtain / create new variable?
public $show_diary = 'my';
private my_diary(){
return 1;
}
public view_diary(){
return ${"this->"}.$this->show_diary.{"_diary()"}; // 1
return $this->.{"$this->show_diary"}._diary() // 2
}
both return nothing.
Your class should be like following:
class Test
{
public $show_diary;
function __construct()
{
$this->show_diary = "my";
}
private function my_diary(){
return 707;
}
public function view_diary(){
echo $this->{$this->show_diary."_diary"}(); // 707
}
}
It almost looks from your question like you are asking about how to turn simple variables into objects and then how to have one object contain another one. I could be way off, but I hope not:
So, first off, what is the differnce between an object and a simple variable? An object is really a collection of (generally) at least one property, which is sort of like a variable within it, and very often functions which do things to the properties of the object. Basically an object is like a complex variable.
In PHP, we need to first declare the strucutre of the object, this is done via a class statement, where we basicaly put the skeleton of what the object will be into place. This is done by the class statement. However, at this point, it hasn't actually been created, it is just like a plan for it when it is created later.
The creation is done via a command like:
$someVariable= new diary();
This executes so create a new variable, and lays it out with the structure, properties and functions defined in the class statement.
From then on, you can access various properties or call functions within it.
class show_diary
{
public $owner;
public function __construct()
{
$this->owner='My';
}
}
class view_diary
{
public $owner;
public $foo;
public function __construct()
{
$this->foo='bar';
$this->owner=new show_diary();
}
}
$diary= new view_diary();
print_r($diary);
The code gives us two classes. One of the classes has an instance of the other class within it.
I have used constructors, which are a special type of function that is executed each time we create a new instance of a class - basically each time we declare a variable of that type, the __construct function is called.
When the $diary= new view_diary(); code is called, it creates an instance of the view_diary class, and in doing so, the first thing it does is assigns it's own foo property to have the value 'bar' in it. Then, it sets it's owner property to be an instance of show_diary which in turn then kicks off the __construct function within the new instance. That in turn assigns the owner property of the child item to have the value 'My'.
If you want to access single properties of the object, you can do so by the following syntax:
echo $diary->foo;
To access a property of an object inside the object, you simply add more arrows:
echo $diary->owner->owner;
Like this?
$diary = $this->show_diary . '_diary';
return $this->$diary();

Is is possible to store a reference to an object method?

Assume this class code:
class Foo {
function method() {
echo 'works';
}
}
Is there any way to store a reference to the method method of a Foo instance?
I'm just experimenting and fiddling around, my goal is checking whether PHP allows to call $FooInstance->method() without writing $FooInstance-> every time. I know I could write a function wrapper for this, but I'm more interested in getting a reference to the instance method.
For example, this pseudo-code would theoretically store $foo->method in the $method variable:
$foo = new Foo();
$method = $foo->method; //Undefined property: Foo::$method
$method();
Apparently, as method is a method and I'm not calling it with () the interpreter thinks I'm looking for a property thus this doesn't work.
I've read through Returning References but the examples only show how to return references to variables, not methods.
Therefore, I've adapted my code to store an anonymous function in a variable and return it:
class Foo {
function &method() {
$fn = function() {
echo 'works';
};
return $fn;
}
}
$foo = new Foo();
$method = &$foo->method();
$method();
This works, but is rather ugly. Also, there's no neat way to call it a single time, as this seems to require storing the returned function in a variable prior to calling it: $foo->method()(); and ($foo->method())(); are syntax errors.
Also, I've tried returning the anonymous function directly without storing it in a variable, but then I get the following notice:
Notice: Only variable references should be returned by reference
Does this mean that returning/storing a reference to a class instance method is impossible/discouraged or am I overlooking something?
Update: I don't mind adding a getter if necessary, the goal is just getting a reference to the method. I've even tried:
class Foo {
var $fn = function() {
echo 'works';
};
function &method() {
return $this->fn;
}
}
But from the unexpected 'function' (T_FUNCTION) error I'd believe that PHP wisely doesn't allow properties to store functions.
I'm starting to believe that my goal isn't easily achievable without the use of ugly hacks as eval().
It is. You have to use an array, with two values: the class instance (or string of the class name if you are calling a static method) and the method name as a string. This is documented on the Callbacks Man page:
A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
Demo (Codepad):
<?php
class Something {
public function abc() {
echo 'called';
}
}
$some = new Something;
$meth = array($some, 'abc');
$meth(); // 'called'
Note this is also works with the built-ins that require callbacks (Codepad):
class Filter {
public function doFilter($value) {
return $value !== 3;
}
}
$filter = new Filter;
$test = array(1,2,3,4,5);
var_dump(array_filter($test, array($filter, 'doFilter'))); // 'array(1,2,4,5)'
And for static methods -- note the 'Filter' instead of an instance of a class as the first element in the array (Codepad):
class Filter {
public static function doFilter($value) {
return $value !== 3;
}
}
$test = array(1,2,3,4,5);
var_dump(array_filter($test, array('Filter', 'doFilter'))); // 'array(1,2,4,5)'
// -------- or -----------
var_dump(array_filter($test, 'Filter::doFilter')); // As of PHP 5.2.3
Yes, you can. PHP has a "callable" pseudo-type, which is, in fact, either just a string or an array. Several functions (usort comes to mind) accept a parameter of the "callback" type: in fact, they just want a function name, or an object-method pair.
That's right, strings are callable:
$fn = "strlen";
$fn("string"); // returns 6
As mentioned, it's possible to use an array as a callback, too. In that case, the first element has to be an object, and the second argument must be a method name:
$obj = new Foo();
$fn = array($obj, "method");
$fn(); // calls $obj->method()
Previously, you had to use call_user_func to call them, but syntax sugar in recent versions make it possible to perform the call straight on variables.
You can read more on the "callable" documentation page.
No, as far as I know it's not possible to store a reference to a method in PHP. Storing object / class name and a method name in an array works, but it's just an array without any special meaning. You can play with the array as you please, for example:
$ref = [new My_Class(), "x"];
// all is fine here ...
$ref();
// but this also valid, now the 'reference' points to My_Other_Class::x()
// do you expect real reference to behave like this?
$ref[0] = new My_Other_Class();
$ref();
// this is also valid syntax, but it throws fatal error
$ref[0] = 1;
$ref();
// let's assume My_Class::y() is a protected method, this won't work outside My_Class
$ref = [new My_Class(), 'y'];
$ref();
this is prone to error as you loose syntax checking due to storing the method name as string.
you can't pass reliably a reference to a private or a protected method this way (unless you call the reference from a context that already has proper access to the method).
Personally I prefer to use lambdas:
$ref = function() use($my_object) { $my_object->x(); }
If you do this from inside $my_object it gets less clunky thanks to access to $this:
$ref = function() { $this->x(); }
this works with protected / private methods
syntax checking works in IDE (less bugs)
unfortunately it's less concise

Design pattern for repetitive switch in getters?

I've an ORM model (PHP Active Record), say, for a blogging system. I've something that's a post model that stores the number of likes. The post could either be a picture or quote (say), and they are different tables (and hence models).
The schema is that a post holds data like number of shares, likes, description, etc. along with either a picture or a quote.
So when writing getters for the post model I'm having to write
public function getX() {
if ($this->isPicture()) {
return $this->picture->getX();
}
else if ($this->isQuote()) {
return $this->quote->getX()
}
else {
return self::DEFAULT_X
}
}
I'm currently having to write this structure for many getter. Is there something I can do to avoid that?
PS: Tagged as PHP because that's my code in.
EDIT
Changed comments to code.
This is a model (and a corresponding table in the DB) that has more data than just a picture and quote. Example, description that's part of the post and doesn't reside on either the picture or the quote.
There's tables for pictures and quotes.
Using PHP Active Record and each of the three classes extends the generic model class provided by PHP Active Record.
The picture model has it's own data. Same for quote.
To expand on the idea of the Strategy pattern mentioned in the comments:
class Post {
// get the correct 'strategy'
public function getModel() {
if ($this->isPicture()) {
return $this->picture;
}
if ($this->isQuote()) {
return $this->quote;
}
return null;
}
// using the strategy
public function getX() {
$model = $this->getModel();
if (null === $model) {
return self::DEFAULT_X;
}
return $model->getX();
}
}
Each strategy would presumably implement the same interface as the Post class for exposing those getters. Even better would be to provide a default strategy (rather than returning null) and have that return the default values. That way, the null check in each getter becomes redundant.
An alternative approach to this is a very basic form of metaprogramming. The idea is that you go a level higher than calling your methods by hand, and let the code do it for you.
(Assume that the method definitions are all part of Post)
public function getX($model = null) {
if ($model) return $model->getX();
else return self::DEFAULT_X;
}
// usage
$postModel->getX($pictureModel);
What's happening here is that, in this single instance of getX in your Post model, you're passing in the name of another class, and executing the `getX' method on that instance (if it exists and is callable).
You can extend this in other ways. For example, maybe you don't want to pass an instance in, when the method can do it anyway:
public function getX($model_name = null) {
if ($model_name && $class_exists($model_name) && is_callable(array($model_name, 'getX')) {
$model = new $model_name;
return $model->getX();
} else {
return self::DEFAULT_X;
}
}
// usage
$postModel->getX('Picture');
In this instance, you pass the model in as a string, and the method will do the rest. While this makes it quicker to get what you want, you might find that you don't want to work with fresh instances all the time (or you can't), so there's a bit of a trade-off with this 'convenient' way.
That still doesn't fully solve your problem, though, since you still have to repeat that for each getter, over and over again. Instead, you can try something like this:
public function __call($method, $args) {
$class = $args[0];
if (class_exists($class) && is_callable(array($class, $method))) {
$model = new $class;
return $model->$method();
}
}
// usage
$postModel->getX('Picture');
$postModel->getY('Quote');
$postModel->getZ('Picture');
If you call a function that doesn't exist on the Post model, that magic method will be called, and it'll fire up a new instance of the model name you supply as an argument, and call the getWhatever method on it, if it exists.
It's important to note that you must not define these getters in Post, unless you want to override the methods in the other classes.
There is still the problem of this creating new instances all the time, though, and to remedy this you can use a bit of dependency injection. This means that you let the Post class contains a list of other instances of classes that it wants to use in future, so you can add and remove them at will.
This is what I would consider the actual solution, with the other examples hopefully showing how I've got here (will edit to clarify things, of course).
public $models = array();
public function addModel($instance) {
$this->models[get_class($instance)] = $instance;
}
public function __call($method, $args) {
$class = $args[0];
if (array_key_exists($class, $this->models)) {
$model = $this->models[$class];
if (is_callable(array($model, $method)) {
return $model->$method();
}
}
}
// usage
$this->addModel($pictureModel);
$this->addModel($quoteModel);
$this->getX('Picture');
$this->getY('Quote');
Here, you're passing in your existing instances of models into the Post class, which then stores them in an array, keyed by the name of the class. Then, when you use the class as described in the last example, instead of creating a new instance, it will use the instance it has already stored. The benefit of this is that you might do things to your instances that you'd want reflected in the Post model.
This means that you can add as many new models as you like that need to plug into Post, and the only thing you need to do is inject them with addModel, and implement the getters on those models.
They all require you to tell the class what models to call at some point or another. Since you have an array of dependent models, why not add a way to get everything?
public function __call($method, $args) {
$class = $args[0];
if (array_key_exists($class, $this->models)) {
$model = $this->models[$class];
if (is_callable(array($model, $method)) {
return $model->$method();
}
} elseif ($class === 'all') {
// return an array containing the results of each method call on each model
return array_map(function($model) use ($method) {
if (is_callable(array($model, $method) return $model->$method();
}, $this->models);
}
}
// usage
$postModel->getX('all');
Using this, you'll get an array containing the return values of each getX method on each model you added with addModel. You can create pretty powerful functions and classes that do all this stuff without you having to repeat tedious logic.
I have to mention that these examples are untested, but at the very least I hope the concept of what you can do has been made clear.
Note:
The same thing can be applied to __GET and __SET methods, too, which are used for accessing properties. It's also worth saying that there may be the slight risk of a library already using these magic methods, in which case you'll need to make the code a little more intelligent.

Categories