How to make __get() called recursively in PHP? - php

How to let C::update2() could access $this->a by __get()? I'm using PHP 7.
<?php
class C
{
public $data = [];
public function __get($p)
{
if (!array_key_exists($p, $this->data))
$this->fetchData(); // should be a properties to update method mapper
return $this->data[$p] ?? null;
}
public function fetchData()
{
$api_data = ['a'=>'123','b'=>'456'];
$this->data['a'] = $api_data['a'];
if ($api_data['b'] == '456') {
$this->fetch2(); // in this method, it'll call some others API
} else {
$this->fetch3(); // also calls API, but different from update2
}
}
public function fetch2()
{
echo json_encode($this->data)."\n";
echo $this->a;
}
public function fetch3()
{
echo json_encode($this->data)."\n";
echo $this->a;
echo $this->b;
}
}
$c = new C;
echo $c->a;
output
{"a":"123"}
PHP Notice: Undefined property: C::$a in /test.php on line 27
PHP Stack trace:
PHP 1. {main}() /test.php:0
PHP 2. C->__get() /test.php:32
PHP 3. C->fetchData() /test.php:9
PHP 4. C->fetch2() /test.php:18
123
The reason I want to do this is I want to make it resolving the properties lazily and without knowing how the properties comes out(black box).

After seeing Nigel Ren's comment, I gave up on using $this->prop syntax in side the class. I use $this->get('prop') instead.
public function __get($property)
{
return $this->get($property);
}
public function get($property)
{
// resolve the property here
}
// other methods inside class
public function method()
{
$this->method2($this->get('prop_1') + $this->get('prop_2'));
}

Related

PHP Magic __invoke upon object-property of a class

Consider this class arrangement - and in particular the magic function __invoke:
class Barman {
public function __construct() {
// .. .constructor stuff - whatever
}
public function makeDrink() {
return "vodka martini, shaken";
}
}
class Bar {
private $arr_barmen = array();
public function __construct() {
$this->arr_barmen['john'] = new Barman();
}
public function __invoke($barman_id) {
echo "I have been invoked";
return $this->arr_barmen[$barman_id];
}
public function aBarFunc($param) {
return "yes it worked ," .$param;
}
}
class Foo {
public $myBar;
public function __construct() {
$this->myBar = new Bar();
}
}
I want to write syntax like this
$company = new Foo();
$company->myBar('john')->makeDrink();
Preferred result:
"vodka martini, shaken"
Actual result:
"Call to undefined method Foo::myBar()"
Invoking myBar() with the magic method should return a barman Object upon which you can call any of the barman's public methods
But now consider this (which does work)
$company = new Foo();
$myBar = $company->myBar;
$drink = $myBar('john')->makeDrink();
echo $drink;
// Result:
// I have been invoked
// vodka martini, shaken
So what's going on? I don't like that workaround - it's not sleek.
I need it to work this way:
$company->myBar('john')->makeDrink();
Please help? :-)
I believe you can just add braces around it:
$company = new Foo();
$drink = ($company->myBar)('john')->makeDrink();
echo $drink; // vodka martini, shaken
This is being caused by an ambiguity in the call you're trying to make:
$company->myBar('john')->makeDrink();
Because myBar is a property, the PHP interpreter isn't expecting it to be callable. It is parsing it as an attempt to call a method called myBar() which doesn't exist, and is thus throwing the error.
The direct way to resolve this is to clarify the ambiguity for the interpreter. You do this by adding curly braces around the property, as follows:
$company->{myBar}('john')->makeDrink();
The code is now explicit that myBar is a property and should be accessed as such, but that it contains a value that is callable and that you wish to make that call.
This whole topic is complicated (slightly) by the fact that PHP 5.x and PHP 7.x behave differently with regard to how they default to handling these kinds of ambiguity. PHP 7 changed the defaults in order to correct some internal inconsistencies within the language. The result is that in situations like this where you have an ambiguity, if you want your code to work across both PHP 5.x and 7.x, you should always use the braces to explicitly define how you want it to work, regardless of whether your code works for you without them.
There is some documentation about this change in the PHP 7.0 upgrade notes, although the examples given don't cover your exact situation.
To attain chaining you have to return the Barman object in invoke.
class Barman {
public function __construct() {
// .. .constructor stuff - whatever
}
public function makeDrink() {
return "vodka martini, shaken";
}
}
class Bar {
private $arr_barmen = array();
public function __construct() {
$this->arr_barmen['john'] = new Barman();
}
public function __invoke($barman_id) {
echo "I have been invoked";
return $this->arr_barmen[$barman_id] = new Barman();
}
public function aBarFunc($param) {
return "yes it worked ," . $param;
}
}
class Foo {
public $myBar;
public function __construct() {
$this->myBar = new Bar();
}
// create a function with variable name to invoke the object
public function myBar($name) {
$mybar = $this->myBar;
return $mybar($name);
}
}
Thank you for the responses. I devised a cute workaround as follows:
class Barman {
public function __construct() {
}
public function makeDrink() {
return "vodka martini, shaken";
}
}
class Bar {
private $arr_barmen = array();
public function __construct() {
$this->arr_barmen['john'] = new Barman();
}
public function getBarman($barman_id) {
return $this->arr_barmen[$barman_id];
}
public function __invoke($barman_id) {
echo "I have been invoked \n";
return $this->arr_barmen[$barman_id];
}
}
class Foo {
private $_myBar;
public function __construct() {
$this->_myBar = new Bar();
}
// The fix
public function myBar($barman_id) {
return $this->_myBar->getBarman($barman_id);
}
}
Usage:
$company = new Foo();
$drink = $company->myBar('john')->makeDrink();
echo $drink; // vodka martini, shaken
How it works?
Foo->myBar becomes private (Foo->$_myBar);
we create a public function with the name myBar inside Foo;
we create a "getBarman" fuction inside Bar which is called from Foo->myBar('john')
A few more steps - and now there's no ambiguity - Foo->myBar() IS always a function.
Cheers
M

Function inside function not working? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I tried to make a function inside a function:
<?php
class usermanager extends datamanager {
public $id;
public $name;
public $from;
public $twitter;
public $instagram;
public $skype;
public $regIP;
public $lastIP;
public $email;
public function __construct($exists = false,$uid = 0) {
if ($exists == true) {
$this->id = $uid;
$this->name = $this->fetch("SELECT * FROM users WHERE ID = '".$uid."';")->name;
public function getProfile() {
profile();
}
}
else {
public function new($name,$password,$email) {
$this->autocommit(false);
if (!($do = $this->query("INSERT INTO users (name,password,email,rank) VALUES ('".$name."',PASSWORD('".$password."'),'".$email."','0');"))) {
$this->rollback();
return false;
}
else {
$this->commit();
return true;
}
} //end new()
} //end else
} //end __construct()
public function __set() {
trigger_error("Can not edit read-only variable",E_USER_ERROR);
} //end __set()
private function profile() {
$gets = array("twitter","instagram","skype","from");
$fetch = $this->fetch("SELECT * FROM users WHERE ID = '".$this->id."';");
foreach ($gets as $get) {
$this->$get = $fetch->$get;
}
}
} //end class
?>
Because I saw this I thought it would work, but I got:
Parse error: syntax error, unexpected T_PUBLIC in /home/a7405987/usermanager.php on line 21
Why doesn't this work?
It is fixed now, but now I'm getting another error:
Call to undefined function getProfile()
How can I fix this?
Defining a function within a function isn't a great idea. Aside from classes, any function definitions are automatically global. The public and private keywords are only valid in a class definition, not within a class function. If you were to remove the public from your inner function definition, it would run without error, but the result would be a globally defined getProfile().
This example should help demonstate the issue:
<?php
class Test {
public function foo() {
function bar() {
echo "Hello from bar!" . PHP_EOL;
}
echo "Hello from foo!" . PHP_EOL;
}
}
$t = new Test;
// PHP Fatal error: Call to undefined method Test::bar()
// $t->bar();
// Works, prints "Hello from foo!"
// bar() is now defined, but not where you expect
$t->foo();
// PHP Fatal error: Call to undefined method Test::bar()
// $t->bar();
// Works, prints "Hello from bar!"
// Note that this is global scope, not from Test
bar();
Demo in action
You cannot use modifiers public/private/protected inside a member function or here a constructor. You can however declare a function inside a method :
public function classMember() {
function doSomething() {
//do something
}
doSomething()
}
For your particular problem, you should instanciate your class and then check if it exists, otherwise insert it.
You cannot change the structure of a class depending on the context it is called

Is it possible to use mixins in php

I came to know about mixins.So my doubt is, is it possible to use mixins in php?If yes then how?
Use Trait introduced in PHP 5.4
<?php
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
?>
which prints Hello World!
http://php.net/manual/en/language.oop5.traits.php
This answer is obsolete as of PHP 5.4. See Jeanno's answer for how to use traits.
It really depends on what level of mixins you want from PHP. PHP handles single-inheritance, and abstract classes, which can get you most of the way.
Of course the best part of mixins is that they're interchangeable snippets added to whatever class needs them.
To get around the multiple inheritance issue, you could use include to pull in snippets of code. You'll likely have to dump in some boilerplate code to get it to work properly in some cases, but it would certainly help towards keeping your programs DRY.
Example:
class Foo
{
public function bar( $baz )
{
include('mixins/bar');
return $result;
}
}
class Fizz
{
public function bar( $baz )
{
include('mixins/bar');
return $result;
}
}
It's not as direct as being able to define a class as class Foo mixin Bar, but it should get you most of the way there. There are some drawbacks: you need to keep the same parameter names and return variable names, you'll need to pass other data that relies on context such as func_get_args_array or __FILE__.
Mixins for PHP (PHP does not implement Mixins natively, but this library will help)
First google result for "php5 mixin": http://www.sitepoint.com/forums/php-application-design-147/ruby-like-mixins-php5-332491.html
First google result for "php mixin": http://www.advogato.org/article/470.html
Short answer: yes, but not natively (yet, evidently, as #mchl notes). Check those out.
Longer answer: if you're using runkit, checkout runkit_method_copy(): "Copies a method from class to another."
I based mixins functionality on the blog entry found at jansch.nl.
class Node
{
protected $__decorator_lookup = array();
public function __construct($classes = array())
{
foreach($classes as $class)
if (class_exists($class))
{
$decorator = new $class($this);
$methods = get_class_methods($decorator);
if (is_array($methods))
foreach($methods as $method)
$this->__decorator_lookup[strtolower($method)] = $decorator;
}
else
trigger_error("Tried to inherit non-existant class", E_USER_ERROR);
}
public function __get($name)
{
switch($name)
{
default:
if ($this->__decorator_lookup[strtolower($name)])
return $this->__call($name);
}
}
public function __call($method, $args = array())
{
if(isset($this->__decorator_lookup[strtolower($method)]))
return call_user_func_array(array($this->__decorator_lookup[strtolower($method)], $method), $args);
else
trigger_error("Call to undefined method " . get_class($this) . "::$method()", E_USER_ERROR);
}
public function __clone()
{
$temp = $this->decorators;
$this->decorators = array();
foreach($temp as $decorator)
{
$new = clone($decorator);
$new->__self = $this;
$this->decorators[] = $new;
}
}
}
class Decorator
{
public $__self;
public function __construct($__self)
{
$this->__self = $__self;
}
public function &__get($key)
{
return $this->__self->$key;
}
public function __call($method, $arguments)
{
return call_user_func_array(array($this->__self, $method), $arguments);
}
public function __set($key, $value)
{
$this->__self->$key = $value;
}
}
class Pretty extends Decorator
{
public function A()
{
echo "a";
}
public function B()
{
$this->b = "b";
}
}
$a = new Node(array("Pretty"));
$a->A(); // outputs "a"
$a->B();
echo($a->b); // outputs "b"
EDIT:
As PHP clone is shallow, added __clone support.
Also, bear in mind that unset WON'T work (or at least I've not managed to make it work) within the mixin. So - doing something like unset($this->__self->someValue); won't unset the value on Node. Don't know why, as in theory it should work. Funny enough unset($this->__self->someValue); var_dump(isset($this->__self->someValue)); will produce correctly false, however accessing the value from Node scope (as Node->someValue) will still produce true. There's some strange voodoo there.

Magic Method __set() on a Instantiated Object

Ok i have a problem, sorry if i cant explaint it clear but the code speaks for its self.
i have a class which generates objects from a given class name;
Say we say the class is Modules:
public function name($name)
{
$this->includeModule($name);
try
{
$module = new ReflectionClass($name);
$instance = $module->isInstantiable() ? $module->newInstance() : "Err";
$this->addDelegate($instance);
}
catch(Exception $e)
{
Modules::Name("Logger")->log($e->getMessage());
}
return $this;
}
The AddDelegate Method:
protected function addDelegate($delegate)
{
$this->aDelegates[] = $delegate;
}
The __call Method
public function __call($methodName, $parameters)
{
$delegated = false;
foreach ($this->aDelegates as $delegate)
{
if(class_exists(get_class($delegate)))
{
if(method_exists($delegate,$methodName))
{
$method = new ReflectionMethod(get_class($delegate), $methodName);
$function = array($delegate, $methodName);
return call_user_func_array($function, $parameters);
}
}
}
The __get Method
public function __get($property)
{
foreach($this->aDelegates as $delegate)
{
if ($delegate->$property !== false)
{
return $delegate->$property;
}
}
}
All this works fine expect the function __set
public function __set($property,$value)
{
//print_r($this->aDelegates);
foreach($this->aDelegates as $k=>$delegate)
{
//print_r($k);
//print_r($delegate);
if (property_exists($delegate, $property))
{
$delegate->$property = $value;
}
}
//$this->addDelegate($delegate);
print_r($this->aDelegates);
}
class tester
{
public function __set($name,$value)
{
self::$module->name(self::$name)->__set($name,$value);
}
}
Module::test("logger")->log("test"); // this logs, it works
echo Module::test("logger")->path; //prints /home/bla/test/ this is also correct
But i cant set any value to class log like this
Module::tester("logger")->path ="/home/bla/test/log/";
The path property of class logger is public so its not a problem of protected or private property access.
How can i solve this issue? I hope i could explain my problem clear.
EDIT:
A simple demonstration
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints 333
What i need is
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints Hello
I realise you already said that path is public, but it's still worth mentioning: If you're using PHP 5.3.0+, note this quirk of property_exists():
5.3.0 | This function checks the existence of a property independent of
accessibility
In other words, if you check if (property_exists($delegate, $property)), you have no guarantee you have access to $delegate->$property for writing (or reading, for that matter, but you are trying to write).
As for actual troubleshooting: You could try checking if your if (property_exists($delegate, $property)) statement actually executes. If it doesn't, check the case of $property.
Sidenote: It's fairly hard to read the code you posted up, which makes it a bit of a pain to troubleshoot. Could you edit your post and indent it properly?
The path property of class logger is public so its not a problem of
protected or private property access.
That's your problem. From the docs:
__set() is run when writing data to inaccessible properties.
That suggests that __set() is not called for public properties.

Reference to static method in PHP?

In PHP, I am able to use a normal function as a variable without problem, but I haven't figured out how to use a static method. Am I just missing the right syntax, or is this not possible?
(EDIT: the first suggested answer does not seem to work. I've extended my example to show the errors returned.)
function foo1($a,$b) { return $a/$b; }
class Bar
{
static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
// WORKS FINE:
$fn = foo1;
print $fn(1,1);
// WORKS FINE:
print self::foo2(2,1);
print Bar::foo2(3,1);
// DOES NOT WORK ... error: Undefined class constant 'foo2'
//$fn = self::foo2;
//print $fn(4,1);
// DOES NOT WORK ... error: Call to undefined function self::foo2()
//$fn = 'self::foo2';
//print $fn(5,1);
// DOES NOT WORK ... error: Call to undefined function Bar::foo2()
//$fn = 'Bar::foo2';
//print $fn(5,1);
}
}
$x = new Bar();
$x->UseReferences();
(I am using PHP v5.2.6 -- does the answer change depending on version too?)
PHP handles callbacks as strings, not function pointers. The reason your first test works is because the PHP interpreter assumes foo1 as a string. If you have E_NOTICE level error enabled, you should see proof of that.
"Use of undefined constant foo1 - assumed 'foo1'"
You can't call static methods this way, unfortunately. The scope (class) is relevant so you need to use call_user_func instead.
<?php
function foo1($a,$b) { return $a/$b; }
class Bar
{
public static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
$fn = 'foo1';
echo $fn(6,3);
$fn = array( 'self', 'foo2' );
print call_user_func( $fn, 6, 2 );
}
}
$b = new Bar;
$b->UseReferences();
In php 5.2, you can use a variable as the method name in a static call, but to use a variable as the class name, you'll have to use callbacks as described by BaileyP.
However, from php 5.3, you can use a variable as the class name in a static call. So:
class Bar
{
public static function foo2($a,$b) { return $a/$b; }
public function UseReferences()
{
$method = 'foo2';
print Bar::$method(6,2); // works in php 5.2.6
$class = 'Bar';
print $class::$method(6,2); // works in php 5.3
}
}
$b = new Bar;
$b->UseReferences();
?>
You could use the full name of static method, including the namespace.
<?php
function foo($method)
{
return $method('argument');
}
foo('YourClass::staticMethod');
foo('Namespace\YourClass::staticMethod');
The name array array('YourClass', 'staticMethod') is equal to it. But I think the string may be more clear for reading.
In PHP 5.3.0, you could also do the following:
<?php
class Foo {
static function Bar($a, $b) {
if ($a == $b)
return 0;
return ($a < $b) ? -1 : 1;
}
function RBar($a, $b) {
if ($a == $b)
return 0;
return ($a < $b) ? 1 : -1;
}
}
$vals = array(3,2,6,4,1);
$cmpFunc = array('Foo', 'Bar');
usort($vals, $cmpFunc);
// This would also work:
$fooInstance = new Foo();
$cmpFunc = array('fooInstance', 'RBar');
// Or
// $cmpFunc = array('fooInstance', 'Bar');
usort($vals, $cmpFunc);
?>
Coming from a javascript background and being spoiled by it, I just coded this:
function staticFunctionReference($name)
{
return function() use ($name)
{
$className = strstr($name, '::', true);
if (class_exists(__NAMESPACE__."\\$className")) $name = __NAMESPACE__."\\$name";
return call_user_func_array($name, func_get_args());
};
}
To use it:
$foo = staticFunctionReference('Foo::bar');
$foo('some', 'parameters');
It's a function that returns a function that calls the function you wanted to call. Sounds fancy but as you can see in practice it's piece of cake.
Works with namespaces and the returned function should work just like the static method - parameters work the same.
This seems to work for me:
<?php
class Foo{
static function Calc($x,$y){
return $x + $y;
}
public function Test(){
$z = self::Calc(3,4);
echo("z = ".$z);
}
}
$foo = new Foo();
$foo->Test();
?>
In addition to what was said you can also use PHP's reflection capabilities:
class Bar {
public static function foo($foo, $bar) {
return $foo . ' ' . $bar;
}
public function useReferences () {
$method = new ReflectionMethod($this, 'foo');
// Note NULL as the first argument for a static call
$result = $method->invoke(NULL, '123', 'xyz');
}
}
"A member or method declared with static can not be accessed with a variable that is an instance of the object and cannot be re-defined in an extending class"
(http://theserverpages.com/php/manual/en/language.oop5.static.php)

Categories