Major Productions
Major Productions

Reputation: 6042

Practical applications of PHP magic methods - __get, __set, and __call

I've generally tried to stay away from PHP's magic methods because they seem to obfuscate an object's public interface. That said, they seem to be used more and more, at least, in the code I've read, so I have to ask: is there any consensus on when to use them? Are there any common patterns for using these three magic methods?

Upvotes: 13

Views: 4721

Answers (7)

FrancescoMM
FrancescoMM

Reputation: 2960

As a sample of usage, a class, Request can read $_REQUEST or _GET, $_POST and provide field values modified or cleaned according to some necessities or for confort.

$in = new Request();

echo $in->field // returns $_REQUEST['field'] if defined, NULL if undefined

echo $in->field(10) // returns $_REQUEST['field'] if defined or 10 as a default

It can use __get to provide value or null for any field, eliminating the tedoius if(isset($_REQUEST['afield'])) checks, and it can use __call to provide the same but accepting some parameter such as a default value, if field is undefined.

class Request {

    public function __get($key) {
        if(array_key_exists($key,$_REQUEST)) return $_REQUEST[$key];
        else return NULL;
    }

    public function __call($key, $arguments) {
        if( array_key_exists($key,$_REQUEST)) return $_REQUEST[$key];
        else if( array_key_exists(0,$arguments)) return $arguments[0];
        else return NULL;
    }

}

$form = new Request();

$formInputName = $form->name('unknown guy');

$formInputDateOfBirth = $form->birthDate;

** CODE IS WRITTEN HERE AS A SAMPLE, SO IT IS UNTESTED **

Upvotes: 0

rich remer
rich remer

Reputation: 3577

Whenever you'd like, as long as the magic properties/methods are documented; undocumented magic should be avoided unless working with a very abstract layer of code, such as when developing an ORM.

acceptable at an abstract layer

/**
 * DB backed model base class.
 */
class Model {
    protected $attributes = [];

    function __get($name) {
        return @$this->attributes[$name];
    }
}

acceptable when documented

/**
 * User model backed by DB tables.
 * @property-read string $first_name
 * @property-read string $last_name
 */
class UserModel extends Model {

}

lazy and unacceptable (and common when using ORMs)

/**
 * This class is magical and awesome and I am a lazy shithead!
 */
class UserModel extends WhoCaresWhenEverythingIsMagical {

}

Upvotes: 0

Spudley
Spudley

Reputation: 168685

It allows you to do things like this:

class myclass {
    private $propertybag;

    public function __get($name) {
        if(isset($this->propertybag[$name]) {return $this->propertybag[$name];}
        throw new Exception("Unknown property " . (string) $name);
    }

 }

Then you can populate $propertybag from a SQL query in a single line, rather than setting a whole bunch of properties one by one.

Also, it allows you to have specific properties which are read-only (ie don't allow them to be modified via __set()). Maybe useful for an ID field, for example.

Also, you can put code into __get() and __set(), so you can do something more complex than just getting or setting a single variable. For example, if you have a storeID field, you may also want to provide a storeName property. You could implement that in __get() via a cross-reference lookup, so you may not need the name actually to be stored in the class. And of course storeName would not want to be implemented in __get().

Lots of possibilities there.

There are of course some down-sides of using magic methods. The biggest one for me is the fact that you lose the auto-complete functionality in your IDE. This may or may not matter to you.

Upvotes: 3

Mel
Mel

Reputation: 6157

One common pattern is to have a single handle for your clients and proxy the calls to encapsulated objects or singletons based on naming conventions or configurations.

class db
{
    static private $instance = null;

    static public function getInstance()
    {
        if( self::$instance == NULL )
            self::$instance = new db;

        return self::$instance;
    }

    function fetch()
    {
        echo "I'm fetching\n";
    }
}

class dataHandler
{
    function __call($name, $argv)
    {
        if( substr($name, 0, 4) == 'data' )
        {
            $fn = substr($name, 4);
            db::getInstance()->$fn($argv);
        }
    }
}

$dh = new dataHandler;
$dh->datafetch('foo', 'bar');

Same principles can be used to drive different backends of the same functionality without having to change the driver.

Upvotes: 0

Michael J.V.
Michael J.V.

Reputation: 5609

Since magic methods can save you a LOT of coding when it comes to repetitive tasks like defining members, populating them and then retrieving them - instead of doing that boring, long piece of work, you can use mentioned 3 methods to shorten the time to code all that. If needed, I can provide a few examples tho they can be found in various tutorials over the net.

I don't know if it's general consensus, but the usual should apply - use where appropriate. If you find yourself to do repetitive task (define member, populate member, get member, call X functions that differ slightly) - magic methods might help you.

Upvotes: 0

Denis de Bernardy
Denis de Bernardy

Reputation: 78443

__call()

I've seen it used to implement behaviors, as in add extra functions to a class through a pluginable interface.

Pseudo-code like so:

$method = function($self) {};
$events->register('object.method', $method);
$entity->method(); // $method($this);

It also makes it easier to write mostly similar functions, such as in ORMs. e.g.:

$entity->setName('foo'); // set column name to 'foo'

__get()/__set()

I've mostly seen it used to wrap access to private variables.

ORMs are the best example that comes to mind:

$entity->name = 'foo'; // set column name to 'foo'

Upvotes: 4

jwueller
jwueller

Reputation: 30996

The main reason is that you do not need to type as much. You could use them for, say, an ORM record and act as implicit setters/getters:

using __call():

$user = new User();
$user->setName("Foo Bar");
$user->setAge(42);
$user->save();

using __set():

$user->name = "Foo Bar";
$user->age = 42;

which maps to a simple array:

array(
    "name" => "Foo Bar",
    "age"  => 42
)

It is much easier to write such an array to the database than doing a lot of manual calls to collect all needed information. __set() and __get() have another advantage over public members: You are able to validate/format your data.

Upvotes: 5

Related Questions