특수 메서드

다음 함수들은 __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() and __debugInfo() PHP 클래스에서는 특별합니다. 이 함수들과 관련된 특별한 사용외에 클래스에서는 해당 함수명을 사용하지 않아야 합니다.

Caution

PHP에서는 모든 특수함수명이 __ 로 시작합니다. 문서화된 특수기능을 위한게 아니라면 함수명을 __ 로 시작하지 않을것을 권장합니다.

__sleep() and __wakeup()

public array __sleep ( void )
void __wakeup ( void )

serialize()는 클래스가 __sleep() 특수함수를 가지는지 확인합니다. 만약에 그렇다면, 직렬화 이전에 실행됩니다. 또한 객체에서 직렬화 가능한 모든 변수들의 배열을 반환할 수 있음을 전제로 객체를 제거할수 있습니다. 만약에 메서드가 다른 어떤것도 리턴하지 않으면, NULL 이 직렬화되고 E_NOTICE를 발생합니다.

Note:

__sleep()에서 부모 클래스의 private 속성의 이름을 반환할 수 없습니다. 그렇게 하면 E_NOTICE 레벨 에러를 발생합니다. 대신에 Serializable 인터페이스를 사용할 수 있을것입니다.

__sleep() 의 의도된 사용은 데이터를 커밋하거나 비슷한 정리 작업을 수행하는 것입니다. 또한, 이 함수는 매우 큰 객체를 완전히 저장할 필요가 없을 경우에 유용합니다.

반대로, unserialize() 는 클래스가 __wakeup() 특수함수를 가지는지 확인합니다. 만약에 그렇다면, 원래의 객체가 가졌던 리소스들을 재건할수 있습니다.

__wakeup()의 의도된 사용은 직렬화 과정에서 끊어진 데이터베이스 커넥션을 재연결하고, 다른 재초기화 작업을 수행하는것입니다.

Example #1 Sleep 과 wakeup

<?php
class Connection
{
    protected 
$link;
    private 
$dsn$username$password;
    
    public function 
__construct($dsn$username$password)
    {
        
$this->dsn $dsn;
        
$this->username $username;
        
$this->password $password;
        
$this->connect();
    }
    
    private function 
connect()
    {
        
$this->link = new PDO($this->dsn$this->username$this->password);
    }
    
    public function 
__sleep()
    {
        return array(
'dsn''username''password');
    }
    
    public function 
__wakeup()
    {
        
$this->connect();
    }
}
?>

__toString()

public string __toString ( void )

__toString() 메서드는 클래스가 문자열로 변환될때의 동작을 결정하도록 해줍니다. 예를 들면, echo $obj; 가 출력할 결과를 보면 됩니다. 메서드는 문자열을 리턴해야 합니다. 그렇지 않을경우 E_RECOVERABLE_ERROR 레벨의 에러가 발생합니다.

Warning

__toString() 메서드 내에서는 예외를 발생할 수 없습니다. 그럴경우에는 치명적인 에러가 발생합니다.

Example #2 간단한 예제

<?php
// Declare a simple class
class TestClass
{
    public 
$foo;

    public function 
__construct($foo)
    {
        
$this->foo $foo;
    }

    public function 
__toString()
    {
        return 
$this->foo;
    }
}

$class = new TestClass('Hello');
echo 
$class;
?>

위 예제의 출력:

Hello

주의할 점은 PHP 5.2.0 이전에는 __toString() 메서드가 오직 echoprint 에만 연결되어 있다는 것입니다. PHP 5.2.0 이후로는, 어떤 문자열 컨텍스트라도 호출할 수 있습니다(예를 들면 printf()%s 와 같은) 그러나 다른 타입의 컨텍스트에는 호출되지 않습니다. (예를 들어 %d 와 같은). PHP 5.2.0 이후로는, __toString()이 없는 객체의 문자열로의 변환은 E_RECOVERABLE_ERROR 를 발생할 것입니다.

__invoke()

mixed __invoke ([ $... ] )

__invoke() 메서드는 스크립트가 객체를 함수로 호출했을때 호출합니다.

Note:

이 기능은 PHP 5.3.0 이후부터 존재합니다.

Example #3 Using __invoke()

<?php
class CallableClass
{
    public function 
__invoke($x)
    {
        
var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>

위 예제의 출력:

int(5)
bool(true)

__set_state()

static object __set_state ( array $properties )

static 메서드는 PHP 5.1.0 이후부터 var_export() 에 의해 내보내진 클래스를 위해 호출됩니다.

이 메서드의 유일한 파라미터는 내보내진 속성을 가진 특정형식(array('property' => value, ...))의 배열입니다.

Example #4 __set_state() 의 사용 (PHP 5.1.0 이후)

<?php

class A
{
    public 
$var1;
    public 
$var2;

    public static function 
__set_state($an_array// As of PHP 5.1.0
    
{
        
$obj = new A;
        
$obj->var1 $an_array['var1'];
        
$obj->var2 $an_array['var2'];
        return 
$obj;
    }
}

$a = new A;
$a->var1 5;
$a->var2 'foo';

eval(
'$b = ' var_export($atrue) . ';'); // $b = A::__set_state(array(
                                            //    'var1' => 5,
                                            //    'var2' => 'foo',
                                            // ));
var_dump($b);

?>

위 예제의 출력:

object(A)#2 (2) {
  ["var1"]=>
  int(5)
  ["var2"]=>
  string(3) "foo"
}

__debugInfo()

array __debugInfo ( void )

이 메서드는 var_dump()에 의해 덤프될때 보여줄 속성을 가져올때 호출됩니다. 만약에 객체에 해당 메서드가 정의되어 있지 않을경우, 모든 public, protected, private 속성을 보여주게 됩니다.

이 기능은 PHP 5.6.0 에서 추가되었습니다.

Example #5 Using __debugInfo()

<?php
class {
    private 
$prop;

    public function 
__construct($val) {
        
$this->prop $val;
    }

    public function 
__debugInfo() {
        return [
            
'propSquared' => $this->prop ** 2,
        ];
    }
}

var_dump(new C(42));
?>

위 예제의 출력:

object(C)#1 (1) {
  ["propSquared"]=>
  int(1764)
}
add a note add a note

User Contributed Notes 46 notes

up
33
jon at webignition dot net
11 years ago
The __toString() method is extremely useful for converting class attribute names and values into common string representations of data (of which there are many choices). I mention this as previous references to __toString() refer only to debugging uses.

I have previously used the __toString() method in the following ways:

- representing a data-holding object as:
   - XML
   - raw POST data
   - a GET query string
   - header name:value pairs

- representing a custom mail object as an actual email (headers then body, all correctly represented)

When creating a class, consider what possible standard string representations are available and, of those, which would be the most relevant with respect to the purpose of the class.

Being able to represent data-holding objects in standardised string forms makes it much easier for your internal representations of data to be shared in an interoperable way with other applications.
up
6
kguest at php dot net
2 years ago
__debugInfo  is also utilised when calling print_r on an object:

$ cat test.php
<?php
class FooQ {

     private
$bar = '';

     public function
__construct($val) {

        
$this->bar = $val;
     }

     public function
__debugInfo()
     {
         return [
'_bar' => $this->bar];
     }
}
$fooq = new FooQ("q");
print_r ($fooq);

$
php test.php
FooQ Object
(
    [
_bar] => q
)
$
up
9
jsnell at e-normous dot com
10 years ago
Be very careful to define __set_state() in classes which inherit from a parent using it, as the static __set_state() call will be called for any children.  If you are not careful, you will end up with an object of the wrong type.  Here is an example:

<?php
class A
{
    public
$var1;

    public static function
__set_state($an_array)
    {
       
$obj = new A;
       
$obj->var1 = $an_array['var1']; 
        return
$obj;
    }
}

class
B extends A {
}

$b = new B;
$b->var1 = 5;

eval(
'$new_b = ' . var_export($b, true) . ';');
var_dump($new_b);
/*
object(A)#2 (1) {
  ["var1"]=>
  int(5)
}
*/
?>
up
1
daniel dot peder at gmail dot com
1 year ago
http://sandbox.onlinephpfunctions.com/code/4d2cc3648aed58c0dad90c7868173a4775e5ba0c

IMHO a bug or need feature change

providing a object as a array index doesn't try to us __toString() method so some volatile object identifier is used to index the array, which is breaking any persistency. Type hinting solves that, but while other than "string" type hinting doesn't work on ob jects, the automatic conversion to string should be very intuitive.

PS: tried to submit bug, but withot patch the bugs are ignored, unfortunately, I don't C coding

<?php

class shop_product_id {
   
    protected
$shop_name;
    protected
$product_id;
   
    function
__construct($shop_name,$product_id){
       
$this->shop_name = $shop_name;
       
$this->product_id = $product_id;
    }

    function
__toString(){
        return
$this->shop_name . ':' . $this->product_id;
    }
}

$shop_name = 'Shop_A';
$product_id = 123;
$demo_id = $shop_name . ':' . $product_id;
$demo_name = 'Some product in shop A';

$all_products = [ $demo_id => $demo_name ];
$pid = new shop_product_id( $shop_name, $product_id );

echo
"with type hinting: ";
echo (
$demo_name === $all_products[(string)$pid]) ? "ok" : "fail";
echo
"\n";

echo
"without type hinting: ";
echo (
$demo_name === $all_products[$pid]) ?  "ok" : "fail";
echo
"\n";
up
8
daan dot broekhof at gmail dot com
7 years ago
Ever wondered why you can't throw exceptions from __toString()? Yeah me too.

Well now you can! This trick allows you to throw any type of exception from within a __toString(), with a full & correct backtrace.

How does it work? Well PHP __toString() handling is not as strict in every case: throwing an Exception from __toString() triggers a fatal E_ERROR, but returning a non-string value from a __toString() triggers a non-fatal E_RECOVERABLE_ERROR.
Add a little bookkeeping, and can circumvented this PHP deficiency!
(tested to work PHP 5.3+)

<?php

set_error_handler
(array('My_ToStringFixer', 'errorHandler'));
error_reporting(E_ALL | E_STRICT);

class
My_ToStringFixer
{
    protected static
$_toStringException;

    public static function
errorHandler($errorNumber, $errorMessage, $errorFile, $errorLine)
    {
        if (isset(
self::$_toStringException))
        {
           
$exception = self::$_toStringException;
           
// Always unset '_toStringException', we don't want a straggler to be found later if something came between the setting and the error
           
self::$_toStringException = null;
            if (
preg_match('~^Method .*::__toString\(\) must return a string value$~', $errorMessage))
                throw
$exception;
        }
        return
false;
    }
   
    public static function
throwToStringException($exception)
    {
       
// Should not occur with prescribed usage, but in case of recursion: clean out exception, return a valid string, and weep
       
if (isset(self::$_toStringException))
        {
           
self::$_toStringException = null;
            return
'';
        }

       
self::$_toStringException = $exception;

        return
null;
    }
}

class
My_Class
{
    public function
doComplexStuff()
    {
        throw new
Exception('Oh noes!');
    }

    public function
__toString()
    {
        try
        {
           
// do your complex thing which might trigger an exception
           
return $this->doComplexStuff();
        }
        catch (
Exception $e)
        {
           
// The 'return' is required to trigger the trick
           
return My_ToStringFixer::throwToStringException($e);
        }
    }
}

$x = new My_Class();

try
{
    echo
$x;
}
catch (
Exception $e)
{
    echo
'Caught Exception! : '. $e;
}
?>
up
5
dhuseby domain getback tld com
11 years ago
The above hint for using array_keys((array)$obj) got me investigating how to get __sleep to really work with object hierarchies.

With PHP 5.2.3, If you want to serialize an object that is part of an object hierarchy and you want to selectively serialize members (public, private, and protected) by manually specifying the array of members, there are a few simple rules for naming members that you must follow:

1. public members should be named using just their member name, like so:

<?php
class Foo {
    public
$bar;

    public function
__sleep() {
        return array(
"bar");
    }
}
?>

2. protected members should be named using "\0" . "*" . "\0" . member name, like so:

<?php
class Foo {
    protected
$bar;

    public function
__sleep() {
        return array(
"\0*\0bar");
    }
}
?>

3. private members should be named using "\0" . class name . "\0" . member name, like so:

<?php
class Foo {
    private
$bar;

    public function
__sleep() {
        return array(
"\0Foo\0bar");
    }
}
?>

So with this information let us serialize a class hierarchy correctly:

<?php

class Base {
    private
$foo = "foo_value";
    protected
$bar = "bar_value";

    public function
__sleep() {
        return array(
"\0Base\0foo", "\0*\0bar");
    }
}

class
Derived extends Base {
    public
$baz = "baz_value";
    private
$boo = "boo_value";

    public function
__sleep() {
       
// we have to merge our members with our parent's
       
return array_merge(array("baz", "\0Derived\0boo"), parent::__sleep());
    }
}

class
Leaf extends Derived {
    private
$qux = "qux_value";
    protected
$zaz = "zaz_value";
    public
$blah = "blah_value";

    public function
__sleep() {
       
// again, merge our members with our parent's
       
return array_merge(array("\0Leaf\0qux", "\0*\0zaz", "blah"), parent::__sleep());
    }
}

// test it
$test = new Leaf();
$s = serialize($test);
$test2 = unserialize($s);
echo
$s;
print_r($test);
print_r($test2);

?>

Now if you comment out all of the __sleep() functions and output the serialized string, you will see that the output doesn't change.  The most important part of course is that with the proper __sleep() functions, we can unserialize the string and get a properly set up object.

I hope this solves the mystery for everybody.  __sleep() does work, if you use it correctly :-)
up
1
smiley at HELLOSPAMBOT dot chillerlan dot net
3 years ago
A simple API wrapper, using __call() and the PHP 5.6 "..." token.
http://php.net/manual/functions.arguments.php#functions.variable-arg-list

<?php
namespace Example;

use
Exception;
use
ReflectionClass;
use
SomeApiInterface;
use
SomeHttpClient;
use
SomeEndpointHandler;

/**
* Class SomeApiWrapper
*
* @method SomeEndpointHandler method1(MethodParams $param1)
* @method SomeEndpointHandler method2(MethodParams $param1, AuthParams $param2 = null)
* ...
* @method SomeEndpointHandler method42()
*/
class SomeApiWrapper{

   
/**
     * @var \SomeHttpClient
     */
   
private $httpClient;

   
/**
     * @var array
     */
   
private $methodMap = [];

   
/**
     * SomeApiWrapper constructor.
     */
   
public function __construct(){
       
$this->mapApiMethods();
       
$this->httpClient = new SomeHttpClient();
    }

   
/**
     * The API is flat and has ~ 150 endpoints, all of which take optional parameters
     * from up to 3 groups (method params, authentication, filters). Instead of
     * implementing the interface and adding countless stubs that have basically
     * the same signature, i just map its methods here and use __call().
     */
   
private function mapApiMethods(){
       
$reflectionClass = new ReflectionClass(SomeApiInterface::class);

        foreach(
$reflectionClass->getMethods() as $m){
           
$this->methodMap[] = $m->name;
        }
    }

   
/**
     * Thanks to the PHP 5.6+ "..." token, there's no hassle with the arguments anymore
     * (ugh, bad pun). Just hand the method parameters into the endpoint handler,
     * along with other mandatory params - type hints are your friends.
     *
     * It's magic!
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return \SomeEndpointHandler
     * @throws \Exception
     */
   
public function __call($method, $arguments){

        if(
in_array($method, $this->methodMap)){
            return new
SomeEndpointHandler($this->httpClient, $method, ...$arguments);
        }

        throw new
Exception('Endpoint "'.$method.'" does not exist');
    }

}
up
1
Anonymous
11 years ago
Serializing objects is problematic with references. This is solved redefining the __sleep() magic method. This is also problematic when parent class has private variables since the parent object is not accessible nor its private variables from within the child object.

I found a solution that seems working for classes that implements this __sleep() method, and for its subclasses. Without more work in subclasses. The inheritance system does the trick.

Recursively __sleep() call parent' __sleep() and return the whole array of variables of the object instance to be serialized.

<?php
class foo {
}

class
a {
  private
$var1;

  function
__construct(foo &$obj = NULL) {
   
$this->var1 = &$obj;
  }

 
/** Return its variables array, if its parent exists and the __sleep method is accessible, call it and push the result into the array and return the whole thing. */
 
public function __sleep() {
   
$a = array_keys(get_object_vars(&$this));
    if (
method_exists(parent, '__sleep')) {
     
$p = parent::__sleep();
     
array_push($a, $p);
    };
    return
$a;
  }
}

class
b extends a {
  function
__construct(foo &$obj = NULL) {
   
parent::__construct($obj);
  }
}

session_start();
$myfoo = &new foo();
$myb = &new b($myfoo);
$myb = unserialize(serialize(&$myb));
?>

This should work, I haven't tested deeper.
up
2
osbertv at yahoo dot com
7 years ago
Invoking a class inside a class results in an error.

<?php
class A
{
    public function
__invoke()
    {
        echo
"Invoking A() Class";
    }
}

class
B
{
    public
$a;
   
    public function
__construct()
    {
       
$this->a = new A();
    }
   
    public function
__invoke()
    {
        echo
"Invoking B() Class";
    }
}

$a = new A();
$b = new B();
$a();
$b();
$b->a();

?>

returns
Invoking B() Class
PHP Fatal error:  Call to undefined method B::a()
up
1
danillo dot paiva dot toledo at gmail dot com
6 years ago
While I was studying Ruby I saw as such interesting things as properties created + its getters and setters in just one line.

I tryied to do the same in PHP and this is the code I have

class Father {
    public function __call($name, $args) {
        if(isset($this->$name)) {
            if(isset($args[0]))
                return $this->$name = $args[0];
            return $this->$name;
        }
        return false;
    }
}

class Child extends Father {
    public $country = "Brazil";
    public $state = "Sao Paulo";
}

Sometimes we don't need things like that on all classes but is quite interesting.
up
3
krisj1010 at gmail.com
14 years ago
If you are attempting to write an abstract/base class which automates the __sleep process in PHP5 you will run into some trouble if the subclasses which are being serialized have private/protected variables you need to be serialized. 

The reason is, even though get_class($this) within the base class will return the subclass -- get_class_vars(get_class($this)) will *not* return the subclass' protected/private variables.  Which makes sense -- using OO principles. 

However, when automating __sleep it becomes necissary to have access to the private/protected subclass variables because their names have to be returned by __sleep.

So here is the work around:
<?php
public function __sleep()
{
...
code ...
$sleepVars    = array_keys((array)$this);
return
$sleepVars;
}
?>

Even though array_keys includes more information about the variable names than just the variable names -- it still seems to work appropriately.
up
2
ddavenport at newagedigital dot com
14 years ago
One of the principles of OOP is encapsulation--the idea that an object should handle its own data and no others'.  Asking base classes to take care of subclasses' data, esp considering that a class can't possibly know how many dozens of ways it will be extended, is irresponsible and dangerous.

Consider the following...

<?php
class SomeStupidStorageClass
{
  public function
getContents($pos, $len) { ...stuff... }
}

class
CryptedStorageClass extends SomeStupidStorageClass
{
  private
$decrypted_block;
  public function
getContents($pos, $len) { ...decrypt... }
}
?>

If SomeStupidStorageClass decided to serialize its subclasses' data as well as its own, a portion of what was once an encrypted thingie could be stored, in the clear, wherever the thingie was stored.  Obviously, CryptedStorageClass would never have chosen this...but it had to either know how to serialize its parent class's data without calling parent::_sleep(), or let the base class do what it wanted to.

Considering encapsulation again, no class should have to know how the parent handles its own private data.  And it certainly shouldn't have to worry that users will find a way to break access controls in the name of convenience.

If a class wants both to have private/protected data and to survive serialization, it should have its own __sleep() method which asks the parent to report its own fields and then adds to the list if applicable.  Like so....

<?php

class BetterClass
{
  private
$content;

  public function
__sleep()
  {
    return array(
'basedata1', 'basedata2');
  }

  public function
getContents() { ...stuff... }
}

class
BetterDerivedClass extends BetterClass
{
  private
$decrypted_block;

  public function
__sleep()
  {
    return
parent::__sleep();
  }

  public function
getContents() { ...decrypt... }
}

?>

The derived class has better control over its data, and we don't have to worry about something being stored that shouldn't be.
up
1
rayRO
13 years ago
If you use the Magical Method '__set()', be shure that the call of
<?php
$myobject
->test['myarray'] = 'data';
?>
will not appear!

For that u have to do it the fine way if you want to use __set Method ;)
<?php
$myobject
->test = array('myarray' => 'data');
?>

If a Variable is already set, the __set Magic Method already wont appear!

My first solution was to use a Caller Class.
With that, i ever knew which Module i currently use!
But who needs it... :]
There are quiet better solutions for this...
Here's the Code:

<?php
class Caller {
    public
$caller;
    public
$module;

    function
__call($funcname, $args = array()) {
       
$this->setModuleInformation();

        if (
is_object($this->caller) && function_exists('call_user_func_array'))
           
$return = call_user_func_array(array(&$this->caller, $funcname), $args);
        else
           
trigger_error("Call to Function with call_user_func_array failed", E_USER_ERROR);
       
       
$this->unsetModuleInformation();
        return
$return;
    }

    function
__construct($callerClassName = false, $callerModuleName = 'Webboard') {
        if (
$callerClassName == false)
           
trigger_error('No Classname', E_USER_ERROR);

       
$this->module = $callerModuleName;

        if (
class_exists($callerClassName))
           
$this->caller = new $callerClassName();
        else
           
trigger_error('Class not exists: \''.$callerClassName.'\'', E_USER_ERROR);

        if (
is_object($this->caller))
        {
           
$this->setModuleInformation();
            if (
method_exists($this->caller, '__init'))
               
$this->caller->__init();
           
$this->unsetModuleInformation();
        }
        else
           
trigger_error('Caller is no object!', E_USER_ERROR);
    }

    function
__destruct() {
       
$this->setModuleInformation();
        if (
method_exists($this->caller, '__deinit'))
           
$this->caller->__deinit();
       
$this->unsetModuleInformation();
    }

    function
__isset($isset) {
       
$this->setModuleInformation();
        if (
is_object($this->caller))
           
$return = isset($this->caller->{$isset});
        else
           
trigger_error('Caller is no object!', E_USER_ERROR);
       
$this->unsetModuleInformation();
        return
$return;
    }

    function
__unset($unset) {
       
$this->setModuleInformation();
        if (
is_object($this->caller)) {
            if (isset(
$this->caller->{$unset}))
                unset(
$this->caller->{$unset});
        }
        else
           
trigger_error('Caller is no object!', E_USER_ERROR);
       
$this->unsetModuleInformation();
    }

    function
__set($set, $val) {
       
$this->setModuleInformation();
        if (
is_object($this->caller))
           
$this->caller->{$set} = $val;
        else
           
trigger_error('Caller is no object!', E_USER_ERROR);
       
$this->unsetModuleInformation();
    }

    function
__get($get) {
       
$this->setModuleInformation();
        if (
is_object($this->caller)) {
            if (isset(
$this->caller->{$get}))
               
$return = $this->caller->{$get};
            else
               
$return = false;
        }
        else
           
trigger_error('Caller is no object!', E_USER_ERROR);
       
$this->unsetModuleInformation();
        return
$return;
    }
   
    function
setModuleInformation() {
       
$this->caller->module = $this->module;
    }

    function
unsetModuleInformation() {
       
$this->caller->module = NULL;
    }
}

// Well this can be a Config Class?
class Config {
    public
$module;

    public
$test;

    function
__construct()
    {
        print(
'Constructor will have no Module Information... Use __init() instead!<br />');
        print(
'--> '.print_r($this->module, 1).' <--');
        print(
'<br />');
        print(
'<br />');
       
$this->test = '123';
    }
   
    function
__init()
    {
        print(
'Using of __init()!<br />');
        print(
'--> '.print_r($this->module, 1).' <--');
        print(
'<br />');
        print(
'<br />');
    }
   
    function
testFunction($test = false)
    {
        if (
$test != false)
           
$this->test = $test;
    }
}

echo(
'<pre>');
$wow = new Caller('Config', 'Guestbook');
print_r($wow->test);
print(
'<br />');
print(
'<br />');
$wow->test = '456';
print_r($wow->test);
print(
'<br />');
print(
'<br />');
$wow->testFunction('789');
print_r($wow->test);
print(
'<br />');
print(
'<br />');
print_r($wow->module);
echo(
'</pre>');
?>

Outputs something Like:

Constructor will have no Module Information... Use __init() instead!
-->  <--

Using of __init()!
--> Guestbook <--

123

456

789

Guestbook
up
0
staff at pro-unreal dot de
6 years ago
To avoid instanciating the parent instead of the inherited class for __set_state() as reported by jsnell, you could use late static binding introduced in PHP 5.3:

<?php
class A {
    public static function
__set_state($data) {
        return new static();
    }
}

class
B extends A {
}

$instance = new B();
eval(
'$test = ' . var_export($instance, true) . ';');
var_dump($test);
// -> object(B)#2 (0) {
// }
?>
up
0
Wesley
8 years ago
Warning __toString can be triggerd more then one time

<?php
if(strstr(substr($obj,0,1024), 'somestuff')
    echo
$obj;
return
'missing somestuff at the start, create container!';

substr() will trigger a __toString aswell as echo $obj;
?>

wich cause a performance issue since it will gather all data twice.

what i used as a hotfix:

<?php
__toString
(){
  if(
null === $this->sToString)
    
$this->sToString = $this->_show();
  return
$this->sToString;
}
?>
up
0
Anonymous
9 years ago
C++-style operator overloading finally makes an appearance with the introduction to __invoke().  Unfortunately, with just '()'.  In that sense, it is no more useful than having a default class method (probably quite useful actually) and not having to type out an entire method name.  Complimenting wbcarts at juno dot com's point class below, the following allows calculating distance between one or more graph points...

<?php

class point {
    public
$x;
    public
$y;

    function
__construct($x=0, $y=0) {
       
$this->x = (int) $x;
       
$this->y = (int) $y;
        }
       
    function
__invoke() {
       
$args = func_get_args();
       
$total_distance = 0;
       
$current_loc = $this;
        foreach (
$args as $arg) {
            if (
is_object($arg) and (get_class($arg) === get_class($this))) {
               
$total_distance += sqrt(pow($arg->x - $current_loc->x, 2) + pow((int) $arg->y - $current_loc->y, 2));
               
$current_loc = $arg;
                }
            else {
               
trigger_error("Arguments must be objects of this class.");
                return;
                }
            }
        return
$total_distance;
        }
   
    }

$p1 = new point(1,1);
$p2 = new point(23,-6);
$p3 = new point(15,20);
echo
$p1($p2,$p3,$p1); // round trip 73.89

?>

Functionally, __invoke() can also be used to mimic the use of variable functions.  Sadly, attempting any calling of __invoke() on a static level will produce a fatal error.
up
0
rudie-de-hotblocks at osu1 dot php dot net
10 years ago
Note also that the constructor is executed also, and before __set_state(), making this magic function less magic, imho, (except for the ability to assign private members).
up
0
yanleech at gmail dot com
11 years ago
Maybe we can using unserialize() & __wakeup() instead "new" when creating a new instance of class.

Consider following codes:

class foo
{
    static public $WAKEUP_STR = 'O:3:"foo":0:{}';
    public function foo(){}
    public function bar(){}
}

$foo = unserialize(foo::$WAKEUP_STR);
up
0
Dérico Filho
12 years ago
Since PHP 5.2.0, you'll always get an error like this:
"Object of class foo could not be converted to string"

When one tries to use an object as string, for instance:

class Test{}
echo new Test();

Thus, one way to avoid this problem is to programme the magic method __toString.

However, in the older versions, it would output a string saying that it was an object together a unique obj id. Therefore, the __toString() method must comply with this behaviour.

My suggestion:

class Test{
    function __toString(){
        if(!isset($this->__uniqid))
            $this->__uniqid = md5(uniqid(rand(), true));
        return(get_class($this)."@".$this->__uniqid);
    }

}

echo new Test();

would output something like this:

Test@6006ba04f5569544c10a588b04849cf7
up
0
martin dot goldinger at netserver dot ch
14 years ago
When you use sessions, its very important to keep the sessiondata small, due to low performance with unserialize. Every class shoud extend from this class. The result will be, that no null Values are written to the sessiondata. It will increase performance.

<?
class BaseObject
{
    function __sleep()
    {
        $vars = (array)$this;
        foreach ($vars as $key => $val)
        {
            if (is_null($val))
            {
                unset($vars[$key]);
            }
        }   
        return array_keys($vars);
    }
};
?>
up
-1
Travis Swicegood
12 years ago
There is no need to use eval() to mimic mixins (i.e., multiple inheritance) within PHP 5.  You only need to:

<?php

class MyClass
{
    private
$_obj = null;
    public function
__construct($obj)
    {
       
$this->_obj = $obj;
    }

    public function
__call($method, $args)
    {
        if (!
method_exists($this->_obj, $method)) {
            throw new
Exception("unknown method [$method]");
        }

        return
call_user_func_array(
            array(
$this->_obj, $method),
           
$args
       
);
    }
}

?>

You could just as easily add an addMixin() method that would allow you to add multiple objects to an array, and then iterate over that array until you found the right method.  As noted, these are referred to as a Mixins in other languages.
up
-1
jeffxlevy at gmail dot com
14 years ago
Intriguing what happens when __sleep() and __wakeup() and sessions() are mixed. I had a hunch that, as session data is serialized, __sleep would be called when an object, or whatever, is stored in _SESSION. true. The same hunch applied when session_start() was called. Would __wakeup() be called? True. Very helpful, specifically as I'm building massive objects (well, lots of simple objects stored in sessions), and need lots of automated tasks (potentially) reloaded at "wakeup" time. (for instance, restarting a database session/connection).
up
-2
michal dot kocarek at seznam dot cz
11 years ago
Remember that setters and getters (__set, __get) will work in your class as long as you NOT SET the property with given name.

If you still want to have the public property definition in the class source code (phpDocumentor, editor code completition, or any other reason) when using these magic methods, simply unset() your public properties inside the constructor.
__set/__get function will be called and code reader will see at first sight, which public properties are available.

Example:
<?php
class user {
  
/**
    * @var int Gets and sets the user ID
    */
  
public $UserID;
   private
$_userID;

   public function
__construct() {

     
// All the magic is in single line:
      // We unset public property, so our setters and getters
      // are used and phpDoc and editors with code completition are happy
     
unset($this->UserID);

   }

   public function
__set($key, $value) {
     
// assign value for key UserID to _userID property
  
}

   public function
__get($key) {
     
// return value of _userID for UserID property
  
}
}
?>
up
-2
andrew dot minerd at sellingsource dot com
11 years ago
Until __sleep is "fixed" (here's hoping), a function that will return ALL members of a given object -- public, protected, AND private:

<?php
       
public function getPropertyNames(array $filter = NULL)
        {
           
$rc = new ReflectionObject($this);
           
$names = array();

            while (
$rc instanceof ReflectionClass)
            {
                foreach (
$rc->getProperties() as $prop)
                {
                    if (!
$filter || !in_array($prop->getName(), $filter))
                       
$names[] = $prop->getName();
                }

               
$rc = $rc->getParentClass();
            }

            return
$names;
        }
?>
up
-1
mastabog at hotmail dot com
14 years ago
In reply to krisj1010 at gmail.com below:

__sleep() handles protected/private properties very well. You should never rely on get_class_vars() to retrieve property names since this function only returns the public properties. Use the Reflection API instead for that purpose. Better yet, if you know which ones you want to save it is always faster to specify the return array manually.
up
-3
tom
9 years ago
Note a common pitfall when using __wakeup.

If you unserialize a datastructure, you may not rely on the parent object to have been fully unserialized by the time __wakeup is called. Example

<?php
class A {
public
$b;
public
$name;
}

class
B extends A {
public
$parent;
public function
__wakeup() {
 
var_dump($parent->name);
}
}

$a = new A();
$a->name = "foo";
$a->b = new B();
$a->b->parent = $a;
$s = serialize($a);
$a = unserialize($s);
?>

Expected output: "foo".
Actual output: NULL.

Reason: $b is unserialized before $name. By the time B::__wakeup is called, $a->name does not yet have a value.

So be aware that the order in which your class variables are defined is important! You need to manually order them by dependencies - or write a __sleep function and order them by depencies there. (Currently I can't tell which option I hate more)
up
-2
jstubbs at work-at dot co dot jp
13 years ago
$myclass->foo['bar'] = 'baz';

When overriding __get and __set, the above code can work (as expected) but it depends on your __get implementation rather than your __set. In fact, __set is never called with the above code. It appears that PHP (at least as of 5.1) uses a reference to whatever was returned by __get. To be more verbose, the above code is essentially identical to:

$tmp_array = &$myclass->foo;
$tmp_array['bar'] = 'baz';
unset($tmp_array);

Therefore, the above won't do anything if your __get implementation resembles this:

function __get($name) {
    return array_key_exists($name, $this->values)
        ? $this->values[$name] : null;
}

You will actually need to set the value in __get and return that, as in the following code:

function __get($name) {
    if (!array_key_exists($name, $this->values))
        $this->values[$name] = null;
    return $this->values[$name];
}
up
-3
igorbt
6 years ago
In recent versions of PHP, if you define __toString with arguments it will trigger a Fatal error: "__tostring() cannot take arguments". But, if you really need this (like I needed, because my framework heavily used these arguments), you have a workaround:
<?php
class a
{
    public function
__toString() {
        list(
$a) = func_get_args();
        return
$a;
    }
}

$a = new a();
echo
$a->__toString('PHP'); // PHP
?>
up
-3
Anonymous
9 years ago
Concerning __set() with protected/private/overloaded properties, the behavior might not be so intuitive without knowing some underlying rules.  Consider this test object for the following examples...

<?php
class A {
    protected
$test_int = 2;
    protected
$test_array = array('key' => 'test');
    protected
$test_obj;
   
    function
__construct() {
       
$this->test_obj = new stdClass();
        }
       
    function
__get($prop) {
        return
$this->$prop;
        }
       
    function
__set($prop, $val) {
       
$this->$prop = $val;
        }
    }

$a = new A();

?>

Combined Operators (.=, +=, *=, etc): you must also define a companion __get() method to grant write -and- read access to the property.  Remember, "$x += $y" is shorthand for "$x = $x + $y".  In other words, "__set($x, (__get($x) + $y))".

Properties that are Arrays: attempting to set array values like "$a->test_array[] = 'asdf';" from outside this object will result in an "Indirect modification of overloaded property" notice and the operation completely ignored.  You can't use '[]' for array value assignment in this context (with the exception only if you made __get() return by reference, in which case, it would work fine and bypass the __set() method altogether).  You can work around this doing something like unioning the array instead:

<?php

$a
->test_array[] = 'asdf'; // notice given and ignored unless __get() was declared to return by reference
$a->test_array += array(1 => 'asdf'); // to add a key/value
$a->test_array = array("key" => 'asdf') + $a->test_array; // to overwrite a  key/value.

?>

Properties that are Objects: as long as you have that __get() method, you can freely access and alter that sub object's own properties, bypassing __set() entirely.  Remember, objects are assigned and passed by reference naturally.

<?php

$a
->test_obj->prop = 1; // fine if $a did not have a set method declared.

?>

All above tested in 5.3.2.
up
-3
b dot schoppmeier at bas-consult dot de
13 years ago
The sequence of events regarding __sleep and __destruct is unusual __ as __destruct is called before __sleep. The following code snippet:

<?php
$sequence
= 0;
class
foo {
    public
$stuff;   
    public function
__construct($param) {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - constructor\n";
       
$this->stuff = $param;
    }
    public function
__destruct() {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - destructor\n";
    }
    public function
__sleep() {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - __sleep\n";
        return array(
"stuff");
    }
    public function
__wakeup() {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - __wakeup\n";
    }
}
session_start();
$_SESSION["obj"] = new foo("A foo");
?>

yields the output:

Seq: 0 - constructor
Seq: 1 - destructor
Seq: 2 - __sleep

Only when you end your script with a call to session_write_close() as in:

<?php
$sequence
= 0;
class
foo {
    public
$stuff;   
    public function
__construct($param) {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - constructor\n";
       
$this->stuff = $param;
    }
    public function
__destruct() {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - destructor\n";
    }
    public function
__sleep() {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - __sleep\n";
        return array(
"stuff");
    }
    public function
__wakeup() {
        global
$sequence;
        echo
"Seq: ", $sequence++, " - __wakeup\n";
    }
}
session_start();
$_SESSION["obj"] = new foo("A foo");
session_write_close();
?>

the sequence is as common sense would expect it to be as the following output shows:

Seq: 0 - constructor
Seq: 1 - __sleep
Seq: 2 - destructor
up
-2
docey
13 years ago
about __sleep and _wakeup, consider using a method like this:

class core
{

var $sub_core; //ref of subcore
var $_sleep_subcore; // place where serialize version of sub_core will be stored

function core(){
  $this->sub_core = new sub_core();
  return true;
}

function __wakeup()
{
  // on wakeup of core, core unserializes sub_core
  // wich it had stored when it was serialized itself
  $this->sub_core = unserialize($this->_sleep_subcore);
  return true;
}

function __sleep()
{
  // sub_core will be serialized when core is serialized.
  // the serialized subcore will be stored as a string inside core.
   $this->_sleep_subcore = serialize($this->sub_core);
   $return_arr[] = "_sleep_subcore";
   return $return_arr;
}

}

class sub_core
{
var $info;

function sub_core()
{
  $this->info["somedata"] = "somedata overhere"
}

function __wakeup()
{
  return true;
}

function __sleep()
{
  $return_arr[] = "info"
  return $return_arr;
}

}

this way subcore is being serialized by core when core is being serialized. subcore handles its own data and core stores it as a serialize string inside itself. on wakeup core unserializes subcore.

this may have a performance cost, but if you have many objects connected this way this is the best way of serializing them. you only need to serialize the the main object wich will serialize all those below which will serialize all those below them again. in effect causing a sort of chainreaction in wich each object takes care of its own info.

offcoarse you always need to store the eventualy serialized string in a safe place. somebody got experience with this way of __wakeup and __sleep.

works in PHP4&5
up
-3
qfox at ya dot ru
7 years ago
For those of you who have the same trouble as osbertv.
<?php
// ...

$a = new A();
$b = new B();
$a();
$b();
$b->a();

?>

PHP Fatal error:  Call to undefined method B::a()

It's because PHP have bug in parsing syntax (a lot of).
Just make it easier to parse and it would work.
For example, like this:
<?php
$c
= $b->a;
$c();
?>

Or this, if you use 5.4 (if you using 5.3 just move call function to the each class which need it or to some base abstract class):
<?php
trait TInnerClosuresInvoker {
  function
__call($method, $args) {
    if (isset(
$this->$method) && is_callable($method)) {
     
$closure = $this->$method;
     
call_user_func_array($closure, $args);
    } else {
     
trigger_error('Call to undefined method '.__CLASS__.'::'.$method.'()', E_USER_ERROR);
    }
  }
}
class
A {
  use
TInnerClosuresInvoker;
  ...
}
?>

It's a little bit dirty, but it works.
up
-5
amir_abiri at ipcmedia dot com
12 years ago
Another small thing that is important to note about __sleep() and privte member variables:

<?php
class A
{
  private
$a;
 
  public function
__construct()
  {
   
$this->a = 1;
  }
}

class
B extends A
{
  protected
$b;
 
  public function
__construct()
  {
   
parent::__construct();
   
$this->b = 2;
  }
 
  function
__sleep()
  {
    return array(
'a', 'b');
  }
}

serialize(new B);
?>

result:
Notice: serialize(): "a" returned as member variable from __sleep() but does not exist in ...

To summerize: in a given class hierarchy in which parent classes contain private member variables, those variables are serialized when __sleep() is not defined. However, once __sleep() is defined, there is no way to make those private member variables serialized as well. From that point on, serialization is performed from the visibility scope of the subclass.

It is particularly important to note this little quirk when designing base classes that their derivables may be serialized, or when subclassing an external library class.
up
-7
hyponiq at gmail dot com
8 years ago
I think it's fair to note the undocumented fact that the __invoke magic method can take any number of arguments (or none).

Example:
<?php
class InvokeNoParams {
    function __invoke()
    {
        print __METHOD__ . PHP_EOL;
        $i = 1;
        foreach (func_get_args() as $arg) {
            print "The value of \$param{$i} is: " . $arg . PHP_EOL;
            ++$i;
        }
        print
PHP_EOL;
    }
}
 
class InvokeSingleParam {
    function __invoke($param1)
    {
        print __METHOD__ . PHP_EOL;
        print "Value of \$param1 is: " . $param1 . PHP_EOL . PHP_EOL;
    }
}
 
class InvokeMultiParams {
    function __invoke($param1, $param2, $param3) {
        print __METHOD__ . PHP_EOL;
        print "Value of \$param1 is: " . $param1 . PHP_EOL;
        print "Value of \$param2 is: " . $param2 . PHP_EOL;
        print "Value of \$param3 is: " . $param3 . PHP_EOL . PHP_EOL;
    }
}
 
$no
= new InvokeNoParams;
$single = new InvokeSingleParam;
$multi = new InvokeMultiParams;
 
$no
(1, 2, 3);
$single('one param');
$multi('param 1', 'param 2', 'param 3');
?>

This outputs:
InvokeNoParams::__invoke
The value of $param1 is: 1
The value of $param2 is: 2
The value of $param3 is: 3

InvokeSingleParam::__invoke
Value of $param1 is: one param

InvokeMultiParams::__invoke
Value of $param1 is: param 1
Value of $param2 is: param 2
Value of $param3 is: param 3
up
-4
alejandro dot gama at gmail dot com
12 years ago
Referering my previus note: there was an error in the code. But i find a better way:

<?
session_start();

class Classes{
  private $name;
  private $statics;
   
  function __construct($name){
    $this->name=$name;
    $this->statics=array();
  }
   
  function setStatic($k,$v){
    if(!is_resource($v))
      $this->statics[$k]=$v;
  }
   
   
  function __wakeup(){
    foreach($this->statics as $k=>$v)
      eval($this->name."::\$".$k."=\$this->statics['".$k."'];");
  }
}

function storeStaticAttributes(){
  $classes=get_declared_classes();
  foreach($classes as $name){
    $reflect=new ReflectionClass($name);

    if($reflect->isUserDefined()){
      $statics=$reflect->getStaticProperties();

      if(empty($_SESSION["_classes"]))
        $_SESSION["_classes"]=array();
           
      if(empty($_SESSION["_classes"][$name]))
        $_SESSION["_classes"][$name]=new Classes($name);

      foreach($statics as $k=>$v)
        $_SESSION["_classes"][$name]->setStatic($k,$v);   
    }
  }
}
register_shutdown_function('storeStaticAttributes');
?>
up
-3
ryan dot jentzsch at gmail dot com
3 years ago
PHP 7+ solves a problem with __invoke noted about 4 yrs ago:

<?php
class a {
    function
__construct() { }
    function
__invoke() { echo("Invoked\n"); }
}

$a = new a();
$a();
// Output: Invoked

class b {
    private
$x;

    function
__construct() {
       
$this->x = new a();
        (
$this->x)();  // Works in PHP 7+
        // $this->x(); // Will blow up in your face: undefined method b::x
   
}
}

$b = new b();
// Output: Invoked
up
-6
moechofe
10 years ago
__invoke() cannot be used to create fluente interface like in the "D language"

<?php
class CallableClass
{
    var
$next;
    function
__invoke($x)
    {
       
var_dump($x);
        return
$this;
   }
}
$obj = new CallableClass;
$obj->next = new CallableClass;
var_dump( $obj(5) ); // OK!
var_dump( $obj(5)(6) ); // Parse error
var_dump( $obj->next(7) ); // Fatal error: Call to undefined method CallableClass::next()
var_dump( {$obj->next}(7) ); // Parse error
?>
up
-3
patricknegri at gmail dot com
10 years ago
Imports Pattern - Extend Classes in Real Time:

<?php
class BaseClass
{
    var
$__imported;
    var
$__imported_functions;
   
    function
__construct()
    {
       
$__imported = Array();
       
$__imported_functions = Array();
    }
   
    function
Imports($object)
    {
       
$new_imports = new $object();
       
$imports_name = get_class($new_imports);
       
array_push( $__imported, Array($imports_name,$new_imports) );
       
$imports_function = get_class_methods($new_imports);
        foreach (
$imports_function as $i=>$function_name)
        {
           
$this->__imported_functions[$function_name] = &$new_imports;
        }       
    }
   
    function
__call($m, $a)
    {   
        if (
array_key_exists($m,$this->__imported_functions))
        {                   
            return
call_user_func_array(Array($this->__imported_functions[$m],$m),$a);
        }
        throw new
ErrorException ('Call to Undefined Method/Class Function', 0, E_ERROR);
    }
}

class
ExternalFunc
{
    function
TestB()
    {
        echo
"External Imported!";
    }
}

class
B extends BaseClass
{
    function
__construct()
    {
       
$this->Imports("ExternalFunc");
    }
   
    function
Msg()
    {
        echo
"Hello world<br />";
    }
}

$b = new B();
$b->Msg();
// or call $b->Imports("ExternalFunc");
$b->TestB();
//$b->TestB(1,3,4);
?>
up
-5
rc @ nospam @ vorklift dot sea oh em
11 years ago
A note: __wakeup occurs before saving the unserialization of an session object.

Therefore, $_SESSION['var']::__wakeup() setting $_SESSION['var'] = new Class() will fail and $_SESSION['var'] will remain unchanged.

This means that if you have a pseudo-temporary object that contains a class to auto revert to, you have to revert that session object in the initialization of the website rather than via a __wakeup() script.
up
-5
taylorbarstow at google's mail service
13 years ago
I've just come accross something interesting relating to storing PHP5 objects in a session.  If you don't provide an __autoload(), then you MUST load the class definition before calling session_start().  I guess that when you call session_start(), any objects in the session are unserialized then and there and placed into $_SESSION.  If you don't provide the class definition before calling session_start(), your object will get the class __PHP_Incomplete_Class, and you won't be able to use it for anything.

Examples:

<?php
session_start
();
require_once
'MyClass.php';
$obj = new MyClass;
$_SESSION['obj'] = $obj;
?>

Works fine.  Then on a subsequent page load:

<?php
session_start
();
require_once
'MyClass.php';
$_SESSION['obj']->callSomeMethod();
?>

Fatal error:  The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "MyClass" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition.

But if you do this instead, it works fine:

<?php
require_once 'MyClass.php';
session_start();
$_SESSION['obj']->callSomeMethod();
?>

Hopefully in some future release of PHP, __PHP_Incomplete_Class will be smart enough to check for a class definition at time of use (method call or property operation), and, if the class exists, magically "complete" itself and turn into the desired object.
up
-4
wbcarts at juno dot com
11 years ago
To be helpful, the __toString() method should return the class name and the state of all its properties inside square brackets.

<?php
class Point {
  protected
$x, $y;

  public function
__construct($xVal = 0, $yVal = 0) {
   
$this->x = $xVal;
   
$this->y = $yVal;
  }   

  public function
__toString() {      // the function we're interested in...
   
return "Point[x=$this->x, y=$this->y]";
  }
}

$point1 = new Point(10, 10);
$point2 = new Point(50, 50);
echo
$point1 . '<br>';
echo
$point2 . '<br><br>';
?>

Point[x=10, y=10]
Point[x=50, y=50]

Classes that include objects, should call that objects __toString() method.

<?php
class Line {
  protected
$start, $end;

  public function
__construct(Point $p1, Point $p2){
   
$this->start = $p1;
   
$this->end = $p2;
  }

  public function
__toString() {      // the function we're interested in...
   
return 'Line[start=' . $this->start->__toString() .  // call __toString()
    
', end=' . $this->end->__toString() . ']';          // call __toString()
 
}
}

echo (new
Line($point1, $point2));
?>

Line[start=Point[x=10, y=10], end=Point[x=50, y=50]]
up
-4
adar at darkpoetry dot de
12 years ago
Maybe not really new and all in all definitely not the best solution,but if you cant extend a class (if your class alreay extends an abstract or other things like that) you can 'fake' a extend.

<?php
class MyClass
       
extends SomeAbstractUnknownClass {

    private
$classObject;

    public function
__construct ( classObject $classToExtend ) {
       
$this->classObject = $classToExtend;
    }

    public function
__call($func, $var) {
        if ( !
count($var) ) {
            return
$this->classObject->$func($var);
        } else {
           
$str = '';
           
$values = array_values($var);
            for (
$i=0; $i<count($values); $i++ ) {
               
$str .= "'".$values[$i]."' ,";
            }  
           
$str = substr($str, 0, -2);
            return eval(
'return $this->classObject->'.$func.'('.$str.');');
        }  
    }  
}
?>

So if you'll do a $myClass->unknownMethod() and it is found neither in MyClass nor in SomeAbstractUnknownClass, MyClass will try to call this method in $classObject.

I use this for 'extending' a UserObject-Class which already extends an other one.

Better solutions are always welcome ;)
up
-7
Voitcus at wp dot pl
7 years ago
You don't need to serialize the class default values, only those which have changed. It might be important for large objects. Note the example below, for simplicity, always serializes arrays and objects.

<?php
class MyBaseClass {
  public
$name='object'; // these are default class values
 
public $test=1;
  public
$test2; // equals to NULL in fact

 
public function __construct(){
   
$this->test2='some text'// this is not a default value, although called in the constructor
 
}

  public function
__sleep(){
   
// default class values:
   
$defaults=get_class_vars(get_class($this)); // not __CLASS__ or self::, if you'd like to use in descendant classes
    // values of $this object:
   
$present=get_object_vars($this);
   
$result=array(); // output array
   
foreach($present as $key=>$value){
      if(!
is_resource($defaults[$key]) && ( // don't store resources
       
is_object($defaults[$key]) ||    // always store objects
       
is_array($defaults[$key])  ||    // and arrays
         
$defaults[$key]!==$value) // and of course all that is not the default value
       
) // tip: try is_scalar as well
     
$result[]=$key;
      }
      return
$result;
  }
}

$obj1=new MyBaseClass();
echo (
$s1=serialize($obj1))."<br>"; // only test2 is stored, as it was changed in the constructor

$obj2=new MyBaseClass();
$obj2->name='object 2'; // change default value here
echo ($s2=serialize($obj2))."<br>"; // stored name and test2

$obj3=new MyBaseClass();
$obj3->test2=NULL; // switch back to default value
echo ($s3=serialize($obj3))."<br>"; // nothing is stored but the class name

// let us check if we can retrieve the objects
unset($obj1, $obj2, $obj3);
$obj1=unserialize($s1);
$obj2=unserialize($s2);
$obj3=unserialize($s3);
var_dump($obj1);
var_dump($obj2);
var_dump($obj3);
?>
up
-6
muratyaman at gmail dot com
10 years ago
Regarding __toString:

<?php
class my_tag_A{

    public
$id='';
    public
$href='';
    public
$target='';
    public
$class='';
   
    public
$label='';
   
    function
__construct($href, $label){
       
$this->href = $href;
       
$this->label = $label;
    }
   
    public function
__toString(){
        return
'<a '.$this->nz_arr(array('id', 'href', 'target', 'class')). ' >' . $this->label . '</a>';
    }
   
    function
nz_arr($attrib_arr){
       
$s = '';
        foreach(
$attrib_arr as $attrib){
           
$s .= $this->nz($attrib);
        }
        return
$s;
    }

   
/**
     * Print the tag attribute if it is not blank, such as id="$this->id"
     * @param string $attrib
     * @return string
     */
   
function nz($attrib){
       
$s = '';
        if(
$this->$attrib != '') $s = $attrib .' = "' . $this->$attrib . '"';
        return
$s;
    }

   
//This causes RECURSION because of parsing between double quotes. This is a very UNEXPECTED behaviour!
   
function nz_($attrib){
       
$s = '';
        if(
$this->$attrib != '') $s = "$attrib = \"$this->$attrib\"";
        return
$s;
    }
   
}
//end  class

//usage
$a = new my_tag_A('abc.php', 'ABC'); $a->target = '_blank';
echo
$a;
//prints:
//    <a href="abc.php" target="_blank" >ABC</a>
?>
up
-8
ksamvel at gmail dot com
13 years ago
To copy base part of derived class appropriate method in base should be defined. E.g.:

  class A {
    public function setAVar( $oAVar) { $this->oAVar = $oAVar; }
    public function getAVar() { return $this->oAVar; }

    public function copyA( &$roDest) {
      if( $roDest instanceof A)
        $this->oAVar = $roDest->oAVar;
    }

    private $oAVar;
  }

  class B extends A {
    public function setBVar( $oBVar) { $this->oBVar = $oBVar; }
    public function getBVar() { return $this->oBVar; }

    private $oBVar;
  }

  $oA = new A();
  $oB = new B();

  $oA->setAVar( 4);

  $oB->setAVar( 5);
  $oB->setBVar( 6);
  echo "oA::oAVar " . $oA->getAVar() . "<br>";
  echo "oB::oAVar " . $oB->getAVar() . "<br>";
  echo "oB::oBVar " . $oB->getBVar() . "<br>";
  echo "<br>";

  $oB->copyA( $oA);

  echo "oA::oAVar " . $oA->getAVar() . "<br>";
  echo "oB::oAVar " . $oB->getAVar() . "<br>";
  echo "oB::oBVar " . $oB->getBVar() . "<br>";

Output:

oA::oAVar 4
oB::oAVar 5
oB::oBVar 6

oA::oAVar 4
oB::oAVar 4
oB::oBVar 6
up
-13
zach at bygeekz dot com
10 years ago
Try this one on.

<?php
$ret
= new Test(true);
var_dump((bool)(string)$ret);
var_dump($ret);
$ret=null;
$ret = new Test();
var_dump((bool)(string)$ret);
var_dump($ret);

class
Test {
    protected
$state=null;
    function
__construct($state=null) {
       
$this->state = $state;
    }
    function
__toString() {
        if (
$this->state) { return "1"; } else { return "0"; }
    }
}
?>

You could for instance do..

if(!(bool)(string)$ret) { do_something!; }

Alternatively, just make state public, and check it.

if(!$ret->state) {}.

There is no automatic way I have found aside from some internal state check to verify a class. It will always return an object. The only way around that is to force it out to string either where I did, or $ret = (string) new Test(); then test the bool of your output..

if (!$ret) { echo "noooo!"; }

But now you have no calling methods, so I hope you passed some data in to get a usable string out.

Of course, if your class isn't named test, you can add a method..

public function test() {
     return $this->state;
}

Logically that will work regardless of the _toString(), but I had hoped to post this to help others see that there are a multitude of ways to check the validity of a class once it is loaded. In __construct you can add any number of checks and set your state appropriately.
To Top