Newsgroup: it.comp.www.php
Titolo: [OT] Di Overloading, Chiusure e pratiche da suicidio Vol. 1

Mi ritrovo a che fare con oggetti molto complessi che vengono recuperati dal sistema mvc tramite oggetti Json (l'evoluzione di questa discussione: 
https://groups.google.com/d/topic/it.comp.www.php/yuhW5r9cjVo/discussion
 )
 
Il problema è che questi oggetti/entità hanno spesso proprietà molto _esose_ da calcolare, quindi l'idea era di avere disponibili immediatamente i dati _grezzi_ (recuperati dal database/cache) e più usati, mentre far si che altre proprietà venissero calcolate al volo solo quando richiesto... una cosa simile alla tecnica _lazy loadings_ di javascript.
 
Ogni entità (nodo, utente, file, form, campo, etc..) potrebbe senza problemi dichiarare indipendentemente quali di questi attributi sono normali e quali sono _pesanti_, per giungere a uno pseudo codice di esempio simile a questo:
 
-----------------------
class Foo{
  public $name;
  private $_dynamic_properties = array('age', /*[...]*/);
 
  public function __construct($n)
  {
    // name è un attibuto _semplice_, recuperato dal model
    // o dalla cache
    $this->name = $n;
  }
 
  public function __get($n)
  {
    if(in_array($n, $this->_dynamic_properties))
    {
      return $this->getDynamicProperty($n);
    }
  }
 
  private function getDynamicProperty($prop)
  {
    // recupero e restituisco il valore di questa proprietà complessa.
    return microtime(true);
  }
}
 
 
$f = new Foo('pippo');
echo $f->age;
-----------------------
 
Ora, questo è l'unico modo (anche se non mi piace per nulla) per gestire la cosa in modo _indolore_ dall'esterno: utilizzando le chiusure infatti bisognerebbe poi accedere agli attributi tramite $f->age(), ma il parser non conosce a priori quali proprietà sono dinamiche o _standard_, quindi accede a tutte senza le parentesi.
Quello che mi piace poco è che sono costretto a implementare hard-coded tutte le proprietà dinamiche, inoltre, perchè il trucco funzioni bisogna che la proprietà non sia dichiarata nell'oggetto (altrimenti il metodo __get non viene richiamato)
Tuttavia mi pare di ricordare che le chiusure occupassero una buona quantità di ram, mentre questo metodo è praticamente _indolore_..
 
 
 
 
Conoscete altre soluzioni più pulite?

 javascript.
 
Non so se ho capito bene quello che ti serve, e nemmeno se puoi 
modificare in qualche modo il tuo codice circostante per adottare questa 
soluzione, ma potresti fare così:
 
class Foo
{
 protected $_data = [];
 
 public function __construct($data)
 {
  $this->_data = $data;
 }
 
 public function __get($k)
 {
  if (array_key_exists($k, $this->_data)) {
   if (is_callable($this->_data[$k]) {
    $fn = $this->_data[$k];
    $this->_data[$k] = $fn();
   }
   return $this->_data[$k];
 
  } else { // ! array_key_exists
   throw new Exception('valore non riconosciuto');
  }
 }
}
 
Questo presuppone che, quando crei $data, tu lo crei in questo modo:
 
$data = [];
$data['valorefisso'] = 'pippo';
$data['valorevariabile'] = function() {
  return microtime();
};
...
 
In pratica nell'array con cui popoli il tuo model devono esserci valori 
"veri" e funzioni che calcolano quei valori.
 
is_callable torna true sia per le anonymous function di PHP 5.3 che per 
le funzioni create con create_function(), che anche per gli oggetti la 
cui classe definisce __invoke(), quindi possono anche essere oggetti veri 
e propri.
 
La prima volta che accedi al valore viene richiamata la callable e viene 
memorizzato il suo risultato, quindi ottieni il lazy load e cachi il 
risultato.
 
Tra l'altro così, per il tuo model Foo è indifferente se un valore è vero 
o con quale strategia venga generato il valore. In pratica fai dependency 
injection dei valori. :D
 
Bye.

Grazie per la risposta!
In effetti il tuo metodo è più pulito ma non credo si adatti alla mia soluzione, perchè le funzioni _pesanti_ nel mio caso fanno parte dell'oggetto stesso (Foo) e non possono essere statiche in quanto hanno bisogno di avere accesso alle proprietà dell'oggetto.
 
Per ora ho pseudo-risolto così:
 
class MyFile{
  // L'oggetto deve dichiarare su quali valori è possibile usare un callback,
  // e anche il nome della funzione. Non voglio ritrovarmi un domani con
  // l'incubo di non sapere la provenienza dei dati
  protected $_dynamic_properties = array('public_url' => 'getPublicUrl', 'values' => 'getValues');
 
  // METHODS
  /**
   * Recupero dinamico di dati sul file che richiedono grandi risorse/tempistiche
   * per essere calcolate (es. url pubblico di un file hostato su Dropbox, dove per averlo
   * bisogna interrogare le API di Dropbox).
   * Un domani sarebbe anche bello salvare i valori nel db in modo da non dover mai fare
   * la richiesta a Dropbox 2 volte per stesso file/proprietà.
   * */
  public function __get($name)
  {
    if(isset($this->_dynamic_properties[$name]))
    {
      $func_name = $this->_dynamic_properties[$name];
      return $this->$func_name();
    }
    else
    {
      throw new ValueException(__METHOD__ . ': property does not exists', $this, array('property' => $name));
    }
  }
  /**/
}
 
 
class PublicFile extends MyFile{
  // Getters
  /**
   * File pubblici: webroot.
   * */
  public function getPublicUrl()
  {
    $public_url = str_replace('public://', $this->_config->get('base_url') . $this->_config->get('url'), $this->filepath);
    $this->public_url = $public_url;
    return $public_url;
  }
  /**/
}
 
class DropboxFile extends MyFile{
  // Getters
  /**
   * File su dropbox
   * */
  public function getPublicUrl()
  {
    // recupero dati account dropbox, chiamata api, etc...
    $this->public_url = $public_url;
    return $public_url;
  }
  /**/
}
 
 
Un problema che _avrei_ avuto con questo metodo, è il discorso della libreria che recupera dati qua e la dal framework utilizzando l'oggetto json: infatti col mio metodo (ma anche col tuo) sia isset() sia property_exists() ritornerebbero false, quindi per le classi che hanno questo workflow dovrei prevedere il controllo.
 
Ma ci ha pensato il mio collega!
Varda te che soluzione!
 
 
if(isset($object->{$property}))
{
  return $object->{$property};
}
else
{
  return $object->{$property};
}
 
 
...meh.