2011-05-11 18 views
29

Estoy diseñando una clase que define un objeto altamente complejo con una tonelada (50+) de parámetros principalmente opcionales, muchos de los cuales tendrían valores predeterminados (por ejemplo: $type = 'foo'; $width = '300'; $interactive = false;). Estoy tratando de determinar la mejor manera de configurar el constructor y variables de instancia/clase con el fin de ser capaz de:PHP - la mejor manera de inicializar un objeto con un gran número de parámetros y valores predeterminados

  • que sea fácil de utilizar la clase
  • facilitan la auto-documento de la clase (es decir: el uso de phpDocumentor) código
  • esta elegantemente

En vista de lo anterior, no quiero ser el constructor de pasar un montón de argumentos. Voy a estar pasando un hash único que contiene los valores de inicialización, por ejemplo: $foo = new Foo(array('type'=>'bar', 'width'=>300, 'interactive'=>false));

En cuanto a la codificación de la clase, todavía se siente como yo preferiría tener ...

class Foo { 
    private $_type = 'default_type'; 
    private $_width = 100; 
    private $_interactive = true; 

    ... 
} 

... porque Creo que esto facilitaría la generación de documentación (obtienes la lista de las propiedades de la clase, que le permite al usuario de la API saber con qué 'opciones' tienen que trabajar), y "se siente" como la forma correcta de hacerlo.

Pero luego se encuentra con el problema de mapear los parámetros entrantes en el constructor a las variables de clase, y sin explotar la tabla de símbolos, entra en un enfoque de "fuerza bruta" que me frustra (aunque yo m abierto a otras opiniones). Por ejemplo:

function __construct($args){ 
    if(isset($args['type'])) $_type = $args['type']; // yuck! 
} 

He considerado crear una sola variable de clase que sea ella misma una matriz asociativa. La inicialización de esto sería muy fácil, entonces, por ejemplo .:

private $_instance_params = array(
    'type' => 'default_type', 
    'width' => 100, 
    'interactive' => true 
); 

function __construct($args){ 
    foreach($args as $key=>$value){ 
     $_instance_params[$key] = $value; 
    } 
} 

Pero esto parece como si no estuviera tomando ventaja de las características nativas como variables de clases privadas, y se siente como la generación de la documentación no va a funcionar con este enfoque.

Gracias por leer hasta aquí; Probablemente estoy preguntando mucho aquí, pero soy nuevo en PHP y realmente estoy buscando la manera idiomática/elegante de hacer esto. ¿Cuáles son tus mejores prácticas?


Adición (detalles acerca de esta clase en particular)

Es muy probable que esta clase está tratando de hacer demasiado, pero es un puerto de una antigua biblioteca de Perl para la creación y procesamiento de formularios. Probablemente exista una forma de dividir las opciones de configuración para aprovechar la herencia y el polimorfismo, pero en realidad puede ser contraproducente.

Por solicitud, aquí hay una lista parcial de algunos de los parámetros (código de Perl). Debería ver que estos no se asignan muy bien a las subclases.

La clase sin duda tiene getters y setters para muchas de estas propiedades para que el usuario pueda anularlas; el objetivo de esta publicación (y algo que el código original hace muy bien) es proporcionar una forma compacta de instanciar estos objetos Form con los parámetros requeridos ya establecidos. Realmente hace un código muy legible.

# Form Behaviour Parameters 
     # -------------------------- 
     $self->{id}; # the id and the name of the <form> tag 
     $self->{name} = "webform"; # legacy - replaced by {id} 
     $self->{user_id} = $global->{user_id}; # used to make sure that all links have the user id encoded in them. Usually this gets returned as the {'i'} user input parameter 
     $self->{no_form}; # if set, the <form> tag will be omitted 
     $self->{readonly}; # if set, the entire form will be read-only 
     $self->{autosave} = ''; # when set to true, un-focusing a field causes the field data to be saved immediately 
     $self->{scrubbed}; # if set to "true" or non-null, places a "changed" radio button on far right of row-per-record forms that indicates that a record has been edited. Used to allow users to edit multiple records at the same time and save the results all at once. Very cool. 
     $self->{add_rowid}; # if set, each row in a form will have a hidden "rowid" input field with the row_id of that record (used primarily for scrubbable records). If the 'scrubbed' parameter is set, this parameter is also automatically set. Note that for this to work, the SELECT statement must pull out a unique row id. 
     $self->{row_id_prefix} = "row_"; # each row gets a unique id of the form id="row_##" where ## corresponds to the record's rowid. In the case of multiple forms, if we need to identify a specific row, we can change the "row_" prefix to something unique. By default it's "row_" 

     $self->{validate_form}; # parses user_input and validates required fields and the like on a form 
     $self->{target}; # adds a target window to the form tag if specified 
     $self->{focus_on_field}; # if supplied, this will add a <script> tag at the end of the form that will set the focus on the named field once the form loads. 
     $self->{on_submit}; # adds the onSubmit event handler to the form tag if supplied 
     $self->{ctrl_s_button_name}; # if supplied with the name of the savebutton, this will add an onKeypress handler to process CTRL-S as a way of saving the form 

     # Form Paging Parameters 
     # ---------------------- 
     $self->{max_rows_per_page}; # when displaying a complete form using printForm() method, determines the number of rows shown on screen at a time. If this is blank or undef, then all rows in the query are shown and no header/footer is produced. 
     $self->{max_pages_in_nav} = 7; # when displaying the navbar above and below list forms, determines how many page links are shown. Should be an odd number 
     $self->{current_offset}; # the current page that we're displaying 
     $self->{total_records}; # the number of records returned by the query 
     $self->{hide_max_rows_selector} = ""; # hide the <select> tag allowing users to choose the max_rows_per_page 
     $self->{force_selected_row} = ""; # if this is set, calls to showPage() will also clear the rowid hidden field on the form, forcing the first record to be displayed if none were selected 
     $self->{paging_style} = "normal"; # Options: "compact" 

Podemos, por supuesto, permitirnos entablar un debate más prolongado sobre el estilo de programación. ¡Pero espero evitarlo, por la cordura de todos los involucrados! Aquí (código Perl, nuevamente) es un ejemplo de creación de instancia de este objeto con un conjunto de parámetros bastante considerable.

my $form = new Valz::Webform (
      id      => "dbForm", 
      form_name    => "user_mailbox_recip_list_students", 
      user_input    => \%params, 
      user_id     => $params{i}, 
      no_form     => "no_form", 
      selectable    => "checkbox", 
      selectable_row_prefix => "student", 
      selected_row   => join (",", getRecipientIDsByType('student')), 
      this_page    => $params{c}, 
      paging_style   => "compact", 
      hide_max_rows_selector => 'true', 
      max_pages_in_nav  => 5 
     ); 
+3

Esto suena como la clase está haciendo waaaaaaaaay demasiado. ¿Puede elaborar más sobre lo que se supone que debe hacer esta clase y tal vez enumerar algunas o más de las 50 propiedades? – Gordon

+0

¿Cuál es el inconveniente de hacer estos miembros públicos? ¿Necesita que se corrijan después de la construcción y no proporciona ningún otro medio para cambiar los valores? – Mel

+0

@Mel - a menos que esté equivocado, hacer que los miembros sean públicos no mejora la situación. Simplemente alentaría un código feo en el front-end en lugar de en el back-end. –

Respuesta

7

Puedo pensar en dos formas de hacerlo. Si desea mantener sus variables de instancia que sólo puede iterar a través de la matriz pasada al constructor y establecer la variable de instancia dinámica:

<?php 

    class Foo { 
     private $_type = 'default_type'; 
     private $_width = 100; 
     private $_interactive = true; 

     function __construct($args){ 
      foreach($args as $key => $val) { 
       $name = '_' . $key; 
       if(isset($this->{$name})) { 
        $this->{$name} = $val; 
       } 
      } 
     } 
    } 

    ?> 

Cuando se utiliza el enfoque de matriz que realmente no tiene que abandonar la documentación. Sólo tiene que utilizar las anotaciones @property en el cuerpo de la clase:

<?php 

/** 
* @property string $type 
* @property integer $width 
* @property boolean $interactive 
*/ 
class Foo { 
    private $_instance_params = array(
     'type' => 'default_type', 
     'width' => 100, 
     'interactive' => true 
    ); 

    function __construct($args){ 
     $this->_instance_params = array_merge_recursive($this->_instance_params, $args); 
    } 

    public function __get($name) 
    { 
     return $this->_instance_params[$name]; 
    } 

    public function __set($name, $value) 
    { 
     $this->_instance_params[$name] = $value; 
    } 
} 

?> 

Dicho esto, una clase con 50 variables miembro es o bien sólo se utiliza para la configuración (que se puede dividir arriba) o simplemente está haciendo demasiado y que podría quiero pensar en refactorizarlo.

+0

Me gustan ambos enfoques, y realmente no había pensado que estaría accediendo a las variables miembro usando $ this y así podría tener acceso programático a ellos. ¿Isset() devuelve true si la variable se declara pero no se le asigna un valor (ni siquiera estoy seguro si eso tiene sentido en PHP). Solo estoy pensando: ¿y si no tengo un valor predeterminado para un cierto valor (por ejemplo, 'private $ _foo;')? –

+0

Yo asignaría nulo a estos. De esa forma puede verificar si (isset ($ this -> {$ name}) || $ this -> {$ name} === null) ... – Daff

+0

Solo para ser claro, si tuviera que implementar su método 1 anterior, ¿debo asignar algún tipo de valor al declarar las propiedades de la clase? es decir: 'private $ foo = null;' y debe evitar 'private $ foo;'? Pensé que isset() devolvió falso si el valor era nulo, y pensé que declarar una variable miembro sin asignarle un valor asignado nulo? –

0

También podría hacer una clase para padres.

En esa clase solo defines las variables.

protected function _SetVarName($arg){ 

    $this->varName=$arg; 
} 

Luego amplíe esa clase a un archivo nuevo y en ese archivo cree todos sus procesos.

para que pueda obtener

classname.vars.php 
classname.php 

classname extends classnameVars { 

} 

Debido a que la mayoría estarán en nivel normal es suficiente con activar/desactivar los que necesita.

$cn=new classname(); 
$cn->setVar($arg);  
//do your functions.. 
+0

Si tuviera que forzar al usuario de la API a hacerlo, ¿por qué no simplemente usar variables públicas en la clase y dejar que el usuario las establezca directamente? –

6

Otro enfoque es crear una instancia de la clase con un objeto FooOptions, actuando únicamente como un contenedor de opciones:

<?php 
class Foo 
{ 
    /* 
    * @var FooOptions 
    */ 
    private $_options; 

    public function __construct(FooOptions $options) 
    { 
     $this->_options = $options; 
    } 
} 


class FooOptions 
{ 
    private $_type = 'default_type'; 
    private $_width = 100; 
    private $_interactive = true; 

    public function setType($type); 
    public function getType(); 

    public function setWidth($width); 
    public function getWidth(); 

    // ... 
} 

Sus opciones están bien documentados y que tienen una manera fácil de configurar/recuperarlos. Esto incluso facilita su prueba, ya que puede crear y establecer diferentes opciones de objetos.

no recuerdo el nombre exacto de este patrón, pero yo creo que es Constructor o Opción patrón.

+0

Esto realmente suena más como un patrón de modelo. No estoy seguro de que su sugerencia realmente funcione bien a menos que defina una interfaz (¿PHP puede hacer eso?) IFooOptions y luego haga que el usuario API implemente esa interfaz en una clase MyOptions, o amplíe esa clase (p. Ej .: MyFooOptions amplíe FooOptions) y pase una instancia de eso para el constructor de Foo. Esto podría funcionar si el usuario solo crea una instancia de Foo varias veces. En situaciones donde el usuario está creando muchas instancias de esta clase, y los parámetros pueden necesitar ser configurados dinámicamente, se torna muy incómodo. –

+0

Sí, PHP puede definir interfaces de la misma manera que las clases: 'interface IFooInterface'. Sin duda, este método tiene sus debilidades y hace que los usuarios sean más difíciles, pero obtienes una API bien definida con tus opciones de clase. Quizás para su caso esta no es la mejor opción ... :) –

2

sólo para seguir con la forma en que he implementado esto, basado en uno de Daff's soluciones: sugerencias

function __construct($args = array()){ 
     // build all args into their corresponding class properties 
     foreach($args as $key => $val) {     
      // only accept keys that have explicitly been defined as class member variables 
      if(property_exists($this, $key)) { 
       $this->{$key} = $val; 
      } 
     } 
    } 

mejora bienvenida!

0

Utilizo esto en algunas de mis clases. Hace que sea fácil de copiar y pegar para un desarrollo rápido.

private $CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType; 
function __construct($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType){ 
    $varsValues = array($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType); 
    $varNames = array('CCNumber', 'ExpMonth', 'ExpYear', 'CV3', 'CardType'); 
    $varCombined = array_combine($varNames, $varsValues); 
    foreach ($varCombined as $varName => $varValue) {$this->$varName = $varValue;} 
} 

pasos para usar:

  1. Pega y obtener la lista de variables a partir de su función __construct actual, la eliminación de cualquier parámetro opcional valores
  2. Si no lo ha hecho, a que, en pasta declare sus variables para su clase, usando el alcance de su elección
  3. Pegue esa misma línea en las líneas $ varValues ​​y $ varNames.
  4. Realice un reemplazo de texto en ", $" por "','". Eso conseguirá todo menos el primero y el último que tendrá que cambiar manualmente
  5. ¡Disfrútelo!
0

Sólo una pequeña mejora en la primera solución de Daff para apoyar propiedades de los objetos que pueden tener un valor por defecto nulo y sería falso de vuelta a la isset() Estado:

<?php 

class Foo { 
    private $_type = 'default_type'; 
    private $_width = 100; 
    private $_interactive = true; 
    private $_nullable_par = null; 

    function __construct($args){ 
     foreach($args as $key => $val) { 
      $name = '_' . $key; 
      if(property_exists(get_called_class(),$name)) 
       $this->{$name} = $val; 
      } 
     } 
    } 
} 

?> 
Cuestiones relacionadas