简体   繁体   中英

OOP PHP, getters and setters using magic methods

What strategies are available to creating accessors and mutators for a PHP class' private members? Is this suggestion good: http://cormacscode.wordpress.com/2009/01/22/read-only-object-variables-in-php-using-magic-methods/

<?php
class classWithReadOnlyVar
{
    private $readOnlyVar;

    public function __get($varName)
    {
        return $this->$varName;
    }

    public function __set($varName,$varValue)
    {
    }
}

What if some members needed a private, public, or protected property method?

First of all, __get , __set , etc. are defined public and you cannot have them otherwise. Magic methods should be used wisely as it takes about three times as long to make a call to a magic method than simply calling a class method.

class A {
   public function __get($name) { ... }

   public function __getValue() { ... }     // <== is faster

}

Usually (normally, preferably), you will have your class members private or protected (never public ) and have accessors and mutators to encapsulate them. Those accessors and mutator may be of any visibility, depending on what the user can do with the members. You may also have immutable classes by declaring only accessors for your members, which are initialized in the constructor only.

So, your sample class should read

class classWithReadOnlyVar {
   private $readOnlyVar;

   public function getReadonlyVar() {
     return $this->readOnlyVar;
   }

}

and should not use the magic methods.

There may be many reasons to avoid using magic methods at all:

  1. they break code completion
  2. they are slower at run-time
  3. they make refactoring and maintenance a bit (lot) harder/complicated
  4. you can't have a protected magic method
  5. etc.

Class members

Their visibility should be private or protected , it all depends if you want them accessible by inheritence. They should never be public as this breaks the OO paradigm.

Example of a protected member:

class A {
    protected $_name = 'A';

    public function getName() { return $this->_name; }
}

class B {
    protected $_name = 'B';   // getName() will not return 'B'
}

(without $_name being protected , this would not be possible, and no need to redefine the accessor)

Accessors

They should be protected or public . Having a private accessor makes no sense; a class should access it's member directly. If the member needs processing, the class will know regardless when to call the accessor or not.

Mutators

They should be protected or public . As for the accessors, it makes no sense to have a private mutator... unless a very rare case when processing needs to be done internally. If a class member has no accessor, it should not have a mutator. It also makes no sense to have a mean to set a value without being able to get the value back somehow.

Having a __set method that does nothing is nonsense. It gives the false impression that properties exist and are accessible when they really are not. Just leave out the empty method and PHP will properly throw errors when you try to modify non-modifiable properties.

Also, any accessor methods can only be public , anything else doesn't make sense. protected and private accessor methods don't make sense, since the only entity able to access them could also access the properties directly.


Summary of the discussion in the comments below:

  • you want to hide properties from code outside the class, since that code is not reliable and can potentially mess up the state of your class
  • code inside the class can reasonably be expected to behave properly with regards to its own properties, so there's no need to protect a class's properties from the class itself
  • a class can always access all its properties directly anyway, providing getters and setters doesn't ensure protection at all (like it does for access from outside the class)

It is not a good idea to use magic methods as __get() and __set() . The first reason being that the code doesn't communicate its intend clearly. Secondly, a client will have to know about which fields are available to be able to use the magic methods. This violates the Law of Demeter that says a module shouldn't know about the innards of the objects it manipulates.

Accessors, mutators and predicates should be named for their value and prefixed with get , set , and is .

For example:

MyClass
{
    /** @var int */
    private $myField;

    /** @return int */
    protected function getMyField()
    {
        $this->myField;
    }
    /** @param int $myField */
    protected function setMyField($myField)
    {
        $this->myField = $myField;
    }
}

You should by all means try to avoid creating public mutators, as it is tempting for external functions to use them the way a procedural program would use a data structure and thereby manipulate the internal state of the object, known as Feature Envy .

The methods of a class should only be interested in the fields and methods of the class they belong to, and not the fields and methods of other classes. When a method uses accessors and mutators of another object to manipulate the data within that object, then it envies the scope of that object. So we want to eliminate Feature Envy because it exposes the internals of one class to another. Sometimes, however, Feature Envy is a necessary evil, eg, in the case where one object's method requires the another object's data to act upon, and moving that object's method into the object holding the data would violate principles of object oriented design.

Why then should we have accessors and mutators if other classes shouldn't use them? Well, making accessors and mutators protected allows for derived classes to access the parent's private fields, yet still shielding the objects internal state from external peeking and poking.

But couldn't we then just set the field to protected and thereby eliminating excess methods of the class? We surely could, but then you don't have the possibility to shield the field from being set by a subclass.

In PHP, we could even argue that it makes sense for a class to use private mutators, so we have the opportunity to type check the value before it is set on the field.

For example:

MyClass
{
    /** @var int */
    private $myField;

    /** @return int */
    protected function getMyField()
    {
        $this->myField;
    }
    /** @param int $myField */
    private function setMyField($myField)
    {
        if (!(is_int($myField) || null === $myField)) {
            throw new \InvalidArgumentException(sprintf(
                '%s expects $myField to be integer; received %s.'
                __METHOD__,
                gettype($myField)
            ));
        }
        $this->myField = $myField;
    }
}

Here an exception is thrown if $myField is not an integer or null . However, it could also be argued that such code is a sign of mistrust to own coding. Regardless, having a private mutator can easily be changed to protected to allow for subclass manipulation of the field, and the accessor method could be set to private or removed to prevent subclasses reading the field.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM