Call a function when I access a property (PHP magic methods) - php

basically I'm using PHP Overloading to create dynamic methods and properties. What I want to do is to trigger a function to a property access but keeping the access to its methods.
In other terms, that's my PHP code:
First Class:
class _class {
private $_instance;
public function __construct() {
$this->_instance = new _object();
}
public function __get($name) {
switch ($name) {
case "instance":
//LOGICS
break;
}
return null;
}
}
Second Class:
class _object {
public function __call($method, $args) {
switch ($method) {
case "method":
//LOGICS
break;
}
return null;
}
}
Now I want to execute a function when I access to an object property in this way:
$obj = new _class();
echo $obj->instance; //some output here, executing a function
echo $obj->instance->method(); //different output, executing the method of the instance
Thanks, an help will be really appreciated!

When you instantiate the first class, then you create a $this->_instance instead $obj->instance. So $obj->instance will be null and can not call anything on that.
But, if you try $obj->_instance->method();, that will be bad also, because _instance is private. So you need to add getter. Try this:
class _class {
private $_instance;
public function __construct() {
$this->_instance = new _object();
}
public function __get($name) {
switch ($name) {
case "instance":
echo "Instance";
break;
}
return null;
}
public function getInstance() {
return $this->_instance;
}
}
class _object {
public function __call($method, $args) {
switch ($method) {
case "method":
echo "Method";
break;
}
return null;
}
}
$obj = new _class();
$obj->instance;
$obj->getInstance()->method();
Output is:
Instance
Method

Related

Php remove () in a function to make it easier to use

I am creating a little system which will allow users to extend the system with their own classes.
Class Core {
static $confArray;
static $extendArray;
protected static $instance;
public function read($name)
{
return self::$confArray[$name];
}
public function put($name, $value)
{
self::$confArray[$name] = $value;
}
public function extend($function, $handler, $args=null){
self::$extendArray[$function] = new $handler($args);
}
public function __call($method, $args){
return self::$extendArray[$method];
}
public static function getInstance()
{
if (!isset(self::$instance))
{
$object =__CLASS__;
self::$instance= new $object;
}
return self::$instance;
}
}
With That, now a user can come and register a simple extension from such a class:
class WorkersTest{
function isWorking($who){
echo "$who is working";
}
function isNotWorking($who){
echo "$who is not working";
}
}
To call the function (isworking/ isNotWorking), a the programmer needs to register the test class through:
Core::getInstance->extend("worker","WorkersTest");
Then it can now be called through:
Core::getInstance->worker()->isWorking("George");
This is working perfectly. My question is how i can remove the () in the call (dont worry why) and have:
Core::getInstance->worker->isWorking("George");
Is it possible?
You can use the magic __get() method, just like __call():
public function __get($name)
{
return $this->$name();
}
Try overriding the magic __get() method to return what you need:
Class Core {
// (...)
public function __get($name) {
if (isset( self::$extendArray[$function] )) {
return $this->$name();
}
//if there is no function registered under named "$name"
//throwing Exception is by design better, as #scragar suggested
throw new Exception("No function registered under named {$name}");
//return NULL;
}
}

PHP Method Chaining: Calling one method before allowing other methods to be chained

Consider the following class
class myClass {
private $model;
public function update($input) {
return $this->model->update($input);
}
public function find($id) {
$this->model = ORMfind($id);
}
}
How do I prevent
$myClass = new myClass;
$myClass->update($input);
The problem isn't HOW to use the above code but how to make update() a method only callable after find().
EDIT: I changed what my method does so it was more clearly understood that I need to do one method (find()) before another (update())
You could add a flag to your code like so:
class myClass {
private $model;
private $canUpdate = 0;
public function update($input) {
if ($canUpdate === 0) return; // or throw an exception here
return $this->model->update($input);
}
public function find($id) {
$this->model = ORMfind($id);
$canUpdate = 1;
}
}
Setting the flag $canUpdate will caution the update() method to react accordingly. If update() is called, you can throw an exception or exit out of the method if the flag is still 0.
To prevent from returning null value by get :
public function get() {
if (isset($this->value)) return $this->value;
else echo "please give me a value ";
}
You can also create a construct:
function __construct($val){
$this->value=$val;
}
and then give a value to your $value without using set() method:
$myClass=new myClass(10);
Outputting text, returning void, I think all of this is wrong. When you do not expect something to happen, you should throw an exception:
class MyClass {
private $canUpdate = false;
public function find($id) {
// some code...
$this->canUpdate = true;
}
public function canUpdate() {
return $this->canUpdate;
}
private function testCanUpdate() {
if (!$this->canUpdate()) {
throw new Exception('You cannot update');
}
}
public function update($inpjut) {
$this->testCanUpdate();
// ... some code
}
}
Now you can do:
$obj = new MyClass();
try {
$obj->update($input);
} catch (Exception $e) {
$obj->find($id);
$obj->update($input);
}
The proper way to make sure ->update() can only be called when the model has been initialized is to turn it into a dependency:
class myClass
{
private $model;
public function __construct($id)
{
$this->model = ORMfind($id);
}
public function update($input) {
return $this->model->update($input);
}
}
$x = new myClass('123');
Alternatively, if you have multiple find operations, you could introduce them as static constructor methods:
class myClass
{
private $model;
private function __construct($model)
{
$this->model = $model;
}
public function update($input) {
return $this->model->update($input);
}
public static function find($id)
{
return new self(ORMfind($id));
}
}
$x = myClass::find('123');
Update
Tackling your immediate problem can be done by a simple check:
public function update($input) {
return $this->model ? $this->model->update($input) : null;
}

Creating a static array without changing thousands of lines of code

We have a class that holds a public array called $saved that contains lots of data required to share between methods (example below)...
class Common {
public $saved = array();
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUserID() {
return $this->saved['user_data']['id'];
}
}
There are literally thousands of lines of code that work like this.
The problem is that new instance of classes that extend Common are being made within some methods so when they access $saved it does not hold the same data.
The solution is to make $saved a static variable, however I can't change all of the references to $this->saved so I want to try and keep the code identical but make it act static.
Here is my attempt to make $this->saved calls static...
class PropertyTest {
private $data = array();
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
}
class Common {
public $saved;
private static $_instance;
public function __construct() {
$this->saved = self::getInstance();
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new PropertyTest();
self::$_instance->foo = array();
}
return self::$_instance->foo;
}
}
This doesn't quite work when setting a variable it doesn't seem to stay static (test case below)...
class Template extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data'] = array('name' => 'bob');
$user = new User();
}
}
class User extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data']['name'] .= " rocks!";
$this->saved['user_data']['id'] = array(400, 10, 20);
}
}
$tpl = new Template();
print_r($tpl->saved['user_data']);
$this->saved is empty when User gets initialized and doesn't seem to be the same variable, the final print_r only shows an array of name => bob.
Any ideas?
First of all, I have to say that, IMO, it is not that good to use an instance's property as a class's property ($saved is not declared as static but its value is shared with all instance).
Here is a working version http://codepad.org/8hj1MOCT, and here is the commented code. Basically, the trick is located in using both ArrayAccess interface and the singleton pattern.
class Accumulator implements ArrayAccess {
private $container = array();
private static $instance = null;
private function __construct() {
}
public function getInstance() {
if( self::$instance === null ) {
self::$instance = new self();
}
return self::$instance;
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
class Common {
public $saved = null;
public function __construct() {
// initialize the "saved" object's property with the singleton
// that variable can be used with the array syntax thanks to the ArrayAccess interface
// so you won't have to modify your actual code
// but also, since it's an object, this local "$this->saved" is a reference to the singleton object
// so any change made to "$this->saved" is in reality made into the Accumulator::$instance variable
$this->saved = Accumulator::getInstance();
}
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUser() {
return $this->saved['user_data'];
}
}
class Template extends Common {
// you can redeclare the variable or not. Since the property is inherited, IMO you should not redeclare it, but it works in both cases
// public $saved = null;
public function __construct() {
// maybe we can move this initialization in a method in the parent class and call that method here
$this->saved = Accumulator::getInstance();
}
}
I think there are a number of issues with this implementation that could well come back to bite you. However, in your current implementation your contructing a new instance (albeit through a static call) every time.
Instead use getInstance() as your singleton hook, and make your __construct private, as you'll only be accessing it from with the context of the Common class.
Like so:
class Common {
public $saved;
private static $_instance;
private function __construct() {
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new self();
... any other modifications you want to make ....
}
return self::$_instance;
}
}
And don't ever run parent::_construct(), instead always use the getInstance() method.
You might also want to ditch the idea of extending this singleton class. This is really a bad antipattern and could cost you a number of issues in the long run. Instead just maintain a Common class that other classes can read / write to. As its a singleton you don't need to worry about injection.
I seem to have solved the problem, by making $this->saved a reference to a static variable it works...
class Common {
private static $savedData = array();
public $saved;
public function __construct() {
$this->saved =& self::$savedData;
}
}

Magic __get getter for static properties in PHP

public static function __get($value)
does not work, and even if it did, it so happens that I already need the magic __get getter for instance properties in the same class.
This probably is a yes or no question, so, it is possible?
No, it is not possible.
Quoting the manual page of __get :
Member overloading only works in
object context. These magic methods
will not be triggered in static
context. Therefore these methods can
not be declared static.
In PHP 5.3, __callStatic has been added ; but there is no __getStatic nor __setStatic yet ; even if the idea of having/coding them often comes back on the php internals# mailling-list.
There is even a Request for Comments: Static classes for PHP
But, still, not implemented (yet ? )
Maybe someone still need this:
static public function __callStatic($method, $args) {
if (preg_match('/^([gs]et)([A-Z])(.*)$/', $method, $match)) {
$reflector = new \ReflectionClass(__CLASS__);
$property = strtolower($match[2]). $match[3];
if ($reflector->hasProperty($property)) {
$property = $reflector->getProperty($property);
switch($match[1]) {
case 'get': return $property->getValue();
case 'set': return $property->setValue($args[0]);
}
} else throw new InvalidArgumentException("Property {$property} doesn't exist");
}
}
Very nice mbrzuchalski. But it seems to only work on public variables. Just change your switch to this to allow it to access private/protected ones:
switch($match[1]) {
case 'get': return self::${$property->name};
case 'set': return self::${$property->name} = $args[0];
}
And you'd probably want to change the if statement to limit the variables that are accessible, or else it would defeat the purpose of having them be private or protected.
if ($reflector->hasProperty($property) && in_array($property, array("allowedBVariable1", "allowedVariable2"))) {...)
So for example, I have a class designed to pull various data for me out of a remote server using an ssh pear module, and I want it to make certain assumptions about the target directory based on what server it's being asked to look at. A tweaked version of mbrzuchalski's method is perfect for that.
static public function __callStatic($method, $args) {
if (preg_match('/^([gs]et)([A-Z])(.*)$/', $method, $match)) {
$reflector = new \ReflectionClass(__CLASS__);
$property = strtolower($match[2]). $match[3];
if ($reflector->hasProperty($property)) {
if ($property == "server") {
$property = $reflector->getProperty($property);
switch($match[1]) {
case 'set':
self::${$property->name} = $args[0];
if ($args[0] == "server1") self::$targetDir = "/mnt/source/";
elseif($args[0] == "server2") self::$targetDir = "/source/";
else self::$targetDir = "/";
case 'get': return self::${$property->name};
}
} else throw new InvalidArgumentException("Property {$property} is not publicly accessible.");
} else throw new InvalidArgumentException("Property {$property} doesn't exist.");
}
}
try this:
class nameClass{
private static $_sData = [];
private static $object = null;
private $_oData = [];
public function __construct($data=[]){
$this->_oData = $data;
}
public static function setData($data=[]){
self::$_sData = $data;
}
public static function Data(){
if( empty( self::$object ) ){
self::$object = new self( self::$_sData );
}
return self::$object;
}
public function __get($key) {
if( isset($this->_oData[$key] ){
return $this->_oData[$key];
}
}
public function __set($key, $value) {
$this->_oData[$key] = $value;
}
}
nameClass::setData([
'data1'=>'val1',
'data2'=>'val2',
'data3'=>'val3',
'datan'=>'valn'
]);
nameClass::Data()->data1 = 'newValue';
echo(nameClass::Data()->data1);
echo(nameClass::Data()->data2);
Combining __callStatic and call_user_func or call_user_func_array can give access to static properties in PHP class
Example:
class myClass {
private static $instance;
public function __construct() {
if (!self::$instance) {
self::$instance = $this;
}
return self::$instance;
}
public static function __callStatic($method, $args) {
if (!self::$instance) {
new self();
}
if (substr($method, 0, 1) == '$') {
$method = substr($method, 1);
}
if ($method == 'instance') {
return self::$instance;
} elseif ($method == 'not_exist') {
echo "Not implemented\n";
}
}
public function myFunc() {
echo "myFunc()\n";
}
}
// Getting $instance
$instance = call_user_func('myClass::$instance');
$instance->myFunc();
// Access to undeclared
call_user_func('myClass::$not_exist');
Also, you can get static properties accessing them like member properties, using __get():
class ClassName {
private static $data = 'smth';
function __get($field){
if (isset($this->$field)){
return $this->$field;
}
if(isset(self::$$field)){
return self::$$field; // here you can get value of static property
}
return NULL;
}
}
$obj = new ClassName();
echo $obj->data; // "smth"

PHP How to distinguish $this pointer in the inheritance chain?

Please look at the following code snipped
class A
{
function __get($name)
{
if ($name == 'service') {
return new Proxy($this);
}
}
function render()
{
echo 'Rendering A class : ' . $this->service->get('title');
}
protected function resourceFile()
{
return 'A.res';
}
}
class B extends A
{
protected function resourceFile()
{
return 'B.res';
}
function render()
{
parent::render();
echo 'Rendering B class : ' . $this->service->get('title');
}
}
class Proxy
{
private $mSite = null;
public function __construct($site)
{
$this->mSite = $site;
}
public function get($key)
{
// problem here
}
}
// in the main script
$obj = new B();
$obj->render();
Question is: in method 'get' of class 'Proxy', how I extract the corresponding resource file name (resourceFile returns the name) by using only $mSite (object pointer)?
What about:
public function get($key)
{
$file = $this->mSite->resourceFile();
}
But this requires A::resourceFile() to be public otherwise you cannot access the method from outside the object scope - that's what access modifiers have been designed for.
EDIT:
OK - now I think I do understand, what you want to achieve. The following example should demonstrate the desired behavior:
class A
{
private function _method()
{
return 'A';
}
public function render()
{
echo $this->_method();
}
}
class B extends A
{
private function _method()
{
return 'B';
}
public function render()
{
parent::render();
echo $this->_method();
}
}
$b = new B();
$b->render(); // outputs AB
But if you ask me - I think you should think about your design as the solution seems somewhat hacky and hard to understand for someone looking at the code.

Categories