overload - Buena práctica: PHP Magic Methods__set y__get
php method constructor (9)
Posible duplicado:
¿Son los métodos mágicos las mejores prácticas en PHP?
Estos son ejemplos simples, pero imagine que tiene más propiedades que dos en su clase.
¿Cuál sería la mejor práctica?
a) Usando __get y __set
class MyClass {
private $firstField;
private $secondField;
public function __get($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function __set($property, $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
}
}
$myClass = new MyClass();
$myClass->firstField = "This is a foo line";
$myClass->secondField = "This is a bar line";
echo $myClass->firstField;
echo $myClass->secondField;
/* Output:
This is a foo line
This is a bar line
*/
b) Usando setters y getters tradicionales
class MyClass {
private $firstField;
private $secondField;
public function getFirstField() {
return $this->firstField;
}
public function setFirstField($firstField) {
$this->firstField = $firstField;
}
public function getSecondField() {
return $this->secondField;
}
public function setSecondField($secondField) {
$this->secondField = $secondField;
}
}
$myClass = new MyClass();
$myClass->setFirstField("This is a foo line");
$myClass->setSecondField("This is a bar line");
echo $myClass->getFirstField();
echo $myClass->getSecondField();
/* Output:
This is a foo line
This is a bar line
*/
En este artículo: http://blog.webspecies.co.uk/2011-05-23/the-new-era-of-php-frameworks.html
El autor afirma que usar métodos mágicos no es una buena idea:
Antes que nada, en aquel entonces era muy popular usar las funciones mágicas de PHP (__get, __call, etc.). No les pasa nada desde el primer vistazo, pero en realidad son realmente peligrosos. Hacen que las API no sean claras, la finalización automática es imposible y lo más importante es que son lentas. El caso de uso para ellos era hackear PHP para hacer cosas que no quería. Y funcionó. Pero hicieron que sucedieran cosas malas.
Pero me gustaría escuchar más opiniones sobre esto.
Ahora estoy volviendo a setters y getters, pero también estoy poniendo getters y setters en la magia methos __get y __set. De esta manera tengo un comportamiento predeterminado cuando hago esto
$ class-> var;
Esto solo llamará al getter que he configurado en el __get. Normalmente usaré el getter directamente, pero todavía hay algunos casos en que esto es simplemente más simple.
Debería usar stdClass si quiere miembros mágicos, si escribe una clase, defina lo que contiene.
El segundo ejemplo de código es una manera mucho más adecuada de hacerlo porque está tomando el control total de los datos que se le dan a la class
. Hay casos en los que __set
y __get
son útiles, pero no en este caso.
Hago una mezcla de la respuesta de edem y tu segundo código. De esta manera, tengo los beneficios de getter / setters comunes (código completado en tu IDE), facilidad de codificación si quiero, excepciones debido a propiedades inexistentes (ideal para descubrir errores tipográficos: $foo->naem
lugar de $foo->name
), lea solo propiedades y propiedades compuestas.
class Foo
{
private $_bar;
private $_baz;
public function getBar()
{
return $this->_bar;
}
public function setBar($value)
{
$this->_bar = $value;
}
public function getBaz()
{
return $this->_baz;
}
public function getBarBaz()
{
return $this->_bar . '' '' . $this->_baz;
}
public function __get($var)
{
$func = ''get''.$var;
if (method_exists($this, $func))
{
return $this->$func();
} else {
throw new InexistentPropertyException("Inexistent property: $var");
}
}
public function __set($var, $value)
{
$func = ''set''.$var;
if (method_exists($this, $func))
{
$this->$func($value);
} else {
if (method_exists($this, ''get''.$var))
{
throw new ReadOnlyException("property $var is read-only");
} else {
throw new InexistentPropertyException("Inexistent property: $var");
}
}
}
}
He estado exactamente en tu caso en el pasado. Y fui por métodos mágicos.
Esto fue un error, la última parte de su pregunta lo dice todo:
- esto es más lento (que getters / setters)
- no hay autocompletado (y esto es un problema importante en realidad), y la administración de tipos por el IDE para la refactorización y la exploración de códigos (en Zend Studio / PhpStorm esto puede manejarse con la anotación
@property
@property, pero eso requiere mantenerlos). : bastante dolor) - la documentación (phpdoc) no concuerda con la forma en que se supone que se utilizará su código, y mirar su clase tampoco trae muchas respuestas. Esto es confuso.
- agregado después de la edición: tener getters para las propiedades es más consistente con los métodos "reales" donde
getXXX()
no solo está devolviendo una propiedad privada sino que hace una lógica real. Tienes el mismo nombre. Por ejemplo, tiene$user->getName()
(devuelve propiedad privada) y$user->getToken($key)
(calculado). El día en que tu getter obtenga más que un getter y necesite hacer algo de lógica, todo sigue siendo consistente.
Finalmente, y este es el mayor problema de la OMI: esto es magia . Y la magia es muy mala, porque tienes que saber cómo funciona la magia para usarla correctamente. Ese es un problema que he conocido en un equipo: todos deben comprender la magia, no solo ustedes.
Getters y setters son un dolor para escribir (los odio) pero valen la pena.
La mejor práctica sería usar getters tradicionales y setters, debido a la introspección o la reflexión. Hay una forma en PHP (exactamente como en Java) para obtener el nombre de un método o de todos los métodos. Tal cosa devolvería "__get" en el primer caso y "getFirstField", "getSecondField" en el segundo (más setters).
Más sobre eso: http://php.net/manual/en/book.reflection.php
Solo necesitas usar magia si el objeto es realmente "mágico". Si tienes un objeto clásico con propiedades fijas, entonces usa setters y getters, funcionan bien.
Si su objeto tiene propiedades dinámicas, por ejemplo, es parte de una capa de abstracción de base de datos, y sus parámetros están establecidos en tiempo de ejecución, entonces usted realmente necesita los métodos mágicos para mayor comodidad.
Uso __get (y propiedades públicas) tanto como sea posible, porque hacen que el código sea mucho más legible. Comparar:
este código dice inequívocamente lo que estoy haciendo:
echo $user->name;
este código me hace sentir estúpido, lo cual no me gusta:
function getName() { return $this->_name; }
....
echo $user->getName();
La diferencia entre los dos es particularmente obvia cuando accede a múltiples propiedades a la vez.
echo "
Dear $user->firstName $user->lastName!
Your purchase:
$product->name $product->count x $product->price
"
y
echo "
Dear " . $user->getFirstName() . " " . $user->getLastName() . "
Your purchase:
" . $product->getName() . " " . $product->getCount() . " x " . $product->getPrice() . " ";
Si "$ a-> b" realmente debería hacer algo o simplemente devolver un valor es responsabilidad del destinatario. Para la persona que llama, "$ user-> name" y "$ user-> accountBalance" deben tener el mismo aspecto, aunque este último puede implicar cálculos complicados. En mis clases de datos utilizo el siguiente método pequeño:
function __get($p) {
$m = "get_$p";
if(method_exists($this, $m)) return $this->$m();
user_error("undefined property $p");
}
cuando alguien llama "$ obj-> xxx" y la clase tiene definido "get_xxx", se llamará implícitamente este método. De modo que puede definir un getter si lo necesita, mientras mantiene su interfaz uniforme y transparente. Como una ventaja adicional, esto proporciona una forma elegante de memorizar cálculos:
function get_accountBalance() {
$result = <...complex stuff...>
// since we cache the result in a public property, the getter will be called only once
$this->accountBalance = $result;
}
....
echo $user->accountBalance; // calculate the value
....
echo $user->accountBalance; // use the cached value
En pocas palabras: php es un lenguaje de scripting dinámico, úselo de esa manera, no pretendas que estás haciendo Java o C #.
Yo voto por una tercera solución. Utilizo esto en mis proyectos y Symfony también usa algo como esto:
public function __call($val, $x) {
if(substr($val, 0, 3) == ''get'') {
$varname = strtolower(substr($val, 3));
}
else {
throw new Exception(''Bad method.'', 500);
}
if(property_exists(''Yourclass'', $varname)) {
return $this->$varname;
} else {
throw new Exception(''Property does not exist: ''.$varname, 500);
}
}
De esta manera tiene getters automatizados (también puede escribir setters), y solo tiene que escribir nuevos métodos si hay un caso especial para una variable miembro.