buenas - Rasgos en PHP: ¿ejemplos/mejores prácticas del mundo real?
buenas practicas php (5)
Traits han sido una de las mayores adiciones para PHP 5.4. Conozco la sintaxis y entiendo la idea detrás de los rasgos, como la reutilización de código horizontal para cosas comunes como el registro, la seguridad, el almacenamiento en caché, etc.
Sin embargo, todavía no sé cómo usaría los rasgos en mis proyectos.
¿Hay algún proyecto de código abierto que ya use rasgos? ¿Algún buen artículo / material de lectura sobre cómo estructurar arquitecturas usando rasgos?
Estoy entusiasmado con los Rasgos porque resuelven un problema común al desarrollar extensiones para la plataforma de comercio electrónico de Magento. El problema se produce cuando las extensiones agregan funcionalidad a una clase principal (como, por ejemplo, el modelo de usuario) extendiéndola. Esto se hace señalando el autocargador Zend (a través de un archivo de configuración XML) para usar el modelo de usuario de la extensión y hacer que ese nuevo modelo amplíe el modelo principal. ( example ) Pero, ¿qué pasa si dos extensiones anulan el mismo modelo? Obtiene una "condición de carrera" y solo se carga una.
La solución en este momento es editar las extensiones para que uno amplíe la clase de anulación de modelo del otro en una cadena, y luego establecer la configuración de la extensión para cargarlos en el orden correcto para que funcione la cadena de herencia.
Este sistema frecuentemente causa errores, y cuando instala extensiones nuevas es necesario verificar conflictos y editar extensiones. Esto es un dolor y rompe el proceso de actualización.
Creo que usar Traits sería una buena forma de lograr lo mismo sin que este modelo molesto anule la "condición de carrera". De acuerdo, aún podría haber conflictos si múltiples Rasgos implementan métodos con los mismos nombres, pero me imagino que algo así como una simple convención de espacio de nombres podría resolver esto en su mayor parte.
TL; DR Creo que los Rasgos podrían ser útiles para crear extensiones / módulos / complementos para grandes paquetes de software PHP como Magento.
Mi opinión personal es que en realidad hay muy poca aplicación de rasgos al escribir código limpio.
En lugar de usar los rasgos para hackear el código en una clase, es mejor pasar las dependencias a través del constructor o vía setters:
class ClassName {
protected $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
// or
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}
La razón principal por la que creo que es mejor que usar rasgos es que tu código es mucho más flexible al eliminar el acoplamiento rígido a un rasgo. Por ejemplo, simplemente podría pasar una clase de registrador diferente ahora. Esto hace que tu código sea reutilizable y comprobable.
Podría tener un rasgo para un objeto de solo lectura como este:
trait ReadOnly{
protected $readonly = false;
public function setReadonly($value){ $this->readonly = (bool)$value; }
public function getReadonly($value){ return $this->readonly; }
}
Podrías detectar si ese rasgo se usa y determinar si o no deberías escribir ese objeto en una base de datos, archivo, etc.
Supongo que uno tendría que buscar en los idiomas que tienen Rasgos desde hace algún tiempo para aprender las buenas / buenas prácticas aceptadas. Mi opinión actual sobre Trait es que solo debes usarlos para el código que deberías duplicar en otras clases que comparten la misma funcionalidad.
Ejemplo para un rasgo Logger:
interface Logger
{
public function log($message, $level);
}
class DemoLogger implements Logger
{
public function log($message, $level)
{
echo "Logged message: $message with level $level", PHP_EOL;
}
}
trait Loggable // implements Logger
{
protected $logger;
public function setLogger(Logger $logger)
{
$this->logger = $logger;
}
public function log($message, $level)
{
$this->logger->log($message, $level);
}
}
class Foo implements Logger
{
use Loggable;
}
Y luego lo haces ( demo )
$foo = new Foo;
$foo->setLogger(new DemoLogger);
$foo->log(''It works'', 1);
Supongo que lo importante a tener en cuenta al usar rasgos es que realmente son solo piezas de código que se copian en la clase. Esto puede conducir fácilmente a conflictos, por ejemplo, cuando intenta cambiar la visibilidad de los métodos, por ejemplo
trait T {
protected function foo() {}
}
class A {
public function foo() {}
}
class B extends A
{
use T;
}
Lo anterior dará como resultado un error ( demo ). Del mismo modo, los métodos declarados en el rasgo que también se declaran en la clase de uso no se copiarán en la clase, por ejemplo
trait T {
public function foo() {
return 1;
}
}
class A {
use T;
public function foo() {
return 2;
}
}
$a = new A;
echo $a->foo();
imprimirá 2 ( demo ). Estas son cosas que querrá evitar porque hacen que los errores sean difíciles de encontrar. También querrás evitar poner cosas en rasgos que operan en propiedades o métodos de la clase que lo usa, por ej.
class A
{
use T;
protected $prop = 1;
protected function getProp() {
return $this->prop;
}
}
trait T
{
public function foo()
{
return $this->getProp();
}
}
$a = new A;
echo $a->foo();
funciona ( demo ) pero ahora el rasgo está íntimamente acoplado a A y se pierde toda la idea de la reutilización horizontal.
Cuando sigas el Principio de segregación de interfaz , tendrás muchas clases e interfaces pequeñas. Eso hace que Traits sea un candidato ideal para las cosas que mencionas, por ejemplo, preocupaciones transversales , pero no para componer objetos (en un sentido estructurado). En nuestro ejemplo de Logger anterior, el rasgo está completamente aislado. No tiene dependencias en clases concretas.
Podríamos usar la aggregation/composition (como se muestra en otra parte de esta página) para lograr la misma clase resultante, pero el inconveniente de usar la agregación / composición es que tendremos que agregar los métodos de proxy / delegador manualmente a cada clase, luego debería ser capaz de iniciar sesión Los rasgos lo resuelven muy bien al permitirme mantener la repetición en un solo lugar y aplicarla selectivamente donde sea necesario.
Nota: dado que los rasgos son un concepto nuevo en PHP, toda opinión expresada anteriormente está sujeta a cambios. Todavía no he tenido mucho tiempo para evaluar el concepto. Pero espero que sea lo suficientemente bueno como para darte algo en qué pensar.
:) No me gusta teorizar y debatir sobre lo que se debería hacer con algo. En este caso, rasgos Le mostraré para qué me parecen útiles los rasgos y puede aprender de ello o ignorarlo.
Rasgos : son geniales para aplicar estrategias . Los patrones de diseño de estrategia, en resumen, son útiles cuando se quiere manejar los mismos datos (filtrados, ordenados, etc.) de manera diferente.
Por ejemplo, tiene una lista de productos que desea filtrar según algunos criterios (marcas, especificaciones, lo que sea) u ordenado por diferentes medios (precio, etiqueta, lo que sea). Puede crear un rasgo de clasificación que contenga diferentes funciones para diferentes tipos de clasificación (numérico, cadena, fecha, etc.). A continuación, puede utilizar este rasgo no solo en su clase de producto (como se muestra en el ejemplo), sino también en otras clases que necesitan estrategias similares (para aplicar una clasificación numérica a algunos datos, etc.).
Intentalo:
<?php
trait SortStrategy {
private $sort_field = null;
private function string_asc($item1, $item2) {
return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
}
private function string_desc($item1, $item2) {
return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
}
private function num_asc($item1, $item2) {
if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
}
private function num_desc($item1, $item2) {
if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
}
private function date_asc($item1, $item2) {
$date1 = intval(str_replace(''-'', '''', $item1[$this->sort_field]));
$date2 = intval(str_replace(''-'', '''', $item2[$this->sort_field]));
if ($date1 == $date2) return 0;
return ($date1 < $date2 ? -1 : 1 );
}
private function date_desc($item1, $item2) {
$date1 = intval(str_replace(''-'', '''', $item1[$this->sort_field]));
$date2 = intval(str_replace(''-'', '''', $item2[$this->sort_field]));
if ($date1 == $date2) return 0;
return ($date1 > $date2 ? -1 : 1 );
}
}
class Product {
public $data = array();
use SortStrategy;
public function get() {
// do something to get the data, for this ex. I just included an array
$this->data = array(
101222 => array(''label'' => ''Awesome product'', ''price'' => 10.50, ''date_added'' => ''2012-02-01''),
101232 => array(''label'' => ''Not so awesome product'', ''price'' => 5.20, ''date_added'' => ''2012-03-20''),
101241 => array(''label'' => ''Pretty neat product'', ''price'' => 9.65, ''date_added'' => ''2012-04-15''),
101256 => array(''label'' => ''Freakishly cool product'', ''price'' => 12.55, ''date_added'' => ''2012-01-11''),
101219 => array(''label'' => ''Meh product'', ''price'' => 3.69, ''date_added'' => ''2012-06-11''),
);
}
public function sort_by($by = ''price'', $type = ''asc'') {
if (!preg_match(''/^(asc|desc)$/'', $type)) $type = ''asc'';
switch ($by) {
case ''name'':
$this->sort_field = ''label'';
uasort($this->data, array(''Product'', ''string_''.$type));
break;
case ''date'':
$this->sort_field = ''date_added'';
uasort($this->data, array(''Product'', ''date_''.$type));
break;
default:
$this->sort_field = ''price'';
uasort($this->data, array(''Product'', ''num_''.$type));
}
}
}
$product = new Product();
$product->get();
$product->sort_by(''name'');
echo ''<pre>''.print_r($product->data, true).''</pre>'';
?>
Como nota final, pienso en rasgos como accesorios (que puedo usar para alterar mis datos). Métodos y propiedades similares que pueden eliminarse de mis clases y colocarse en un solo lugar, para un mantenimiento sencillo, un código más corto y limpio.