2012-10-21 9 views
18

Esempio # 2 da manuale PHP http://php.net/manual/en/language.oop5.traits.php statiTratto PHP: esiste un modo corretto per garantire che la classe che utilizza un tratto estenda una super classe che contiene un determinato metodo?

<?php 
class Base { 
    public function sayHello() { 
     echo 'Hello '; 
    } 
} 

trait SayWorld { 
    public function sayHello() { 
     parent::sayHello(); 
     echo 'World!'; 
    } 
} 

class MyHelloWorld extends Base { 
    use SayWorld; 
} 

$o = new MyHelloWorld(); 
$o->sayHello(); 
?> 

Questo è il codice corretto, ma non è sicuro da usare parent:: in quel contesto. Diciamo che ho scritto la mia stessa classe 'Ciao mondo', che non eredita tutte le altre classi:

<?php 
class MyOwnHelloWorld 
{ 
    use SayWorld; 
} 
?> 

Questo codice non produrrà errori fino a quando chiamo il metodo sayHello(). Questo non va bene.

D'altra parte se il tratto deve utilizzare un certo metodo Posso scrivere questo metodo come astratto, e questo è buono in quanto garantisce che il tratto sia usato correttamente in fase di compilazione. Ma questo non vale per le classi genitore:

<?php 
trait SayWorld 
{ 
    public function sayHelloWorld() 
    { 
     $this->sayHello(); 
     echo 'World!'; 
    } 

    public abstract function sayHello(); // compile-time safety 

} 

Quindi la mia domanda è: C'è un modo per garantire (in fase di compilazione, non in fase di esecuzione), quella classe che utilizza un certo tratto avrà parent::sayHello() metodo?

+0

Quello che puoi fare è aggiungere un metodo astratto per forzare la classe usando quel tratto per avere il metodo che ti serve. in questo modo diventa indipendente dalla gerarchia di classe (l'idea dietro i tratti: P) – Jarry

risposta

4

No, non c'è. In effetti, questo esempio è pessimo, poiché lo scopo dell'introduzione di tratti era di introdurre la stessa funzionalità a molte classi senza fare affidamento sull'ereditarietà, e l'uso di parent non solo richiede che la classe abbia genitore, ma dovrebbe anche avere un metodo specifico.

Su una nota a margine, le chiamate parent non vengono controllate al momento della compilazione, è possibile definire una classe semplice che non estenda nulla con le chiamate padre nei suoi metodi, e funzionerà finché non verrà chiamato uno di questi metodi.

+0

Grazie per questo. Sono consapevole che le classi semplici non controllano le chiamate parent in fase di compilazione, ma è pur sempre un codice corretto. Il mio IDE sempre così saggia può capire la chiamata genitore, il che significa che quando passerò da PHP a un linguaggio fortemente tipizzato sarà comunque il codice corretto. Questo mi permette di dormire la notte. Ma quando si tratta di ereditarietà multipla, non sono a conoscenza di nessun altro linguaggio che non possa fare proprio questo. Un concetto di tratti in Scala consente l'ereditarietà in entrambi i modi. Le classi C++ e Python possono ereditare più 'genitori'. –

+0

Quindi penso che cercherò un modo per aggirare le limitazioni del PHP che renderebbero il mio codice un po 'più sicuro per i tipi. –

0

Penso che ci sia un modo completamente privo di tratti:

class BaseClass 
{ 
    public function sayHello() { 
      echo 'Hello '; 
    } 
} 

class SayWorld 
{ 
    protected $parent = null; 

    function __construct(BaseClass $base) { 
     $this->parent = $base; 
    } 

    public function sayHelloWorld() 
    { 
     $this->parent->sayHello(); 
     echo 'World!'; 
    } 
} 

class MyHelloWorld extends Base { 
    protected $SayWorld = null; 

    function __construct() { 
     $this->SayWorld = new SayWorld($this); 
    } 

    public function __call (string $name , array $arguments) { 
     if(method_exists($this->SayWorld, $name)) { 
      $this->SayWorld->$name(); 
     } 
    } 
} 
2

È possibile controllare se $ this estende una classe specifica o implementa una specifica interfaccia:

interface SayHelloInterface { 
    public function sayHello(); 
} 

trait SayWorldTrait { 
    public function sayHello() { 
     if (!in_array('SayHello', class_parents($this))) { 
      throw new \LogicException('SayWorldTrait may be used only in classes that extends SayHello.'); 
     } 
     if (!$this instanceof SayHelloInterface) { 
      throw new \LogicException('SayWorldTrait may be used only in classes that implements SayHelloInterface.'); 
     } 
     parent::sayHello(); 
     echo 'World!'; 
    } 
} 

class SayHello { 
    public function sayHello() { 
     echo 'Hello '; 
    } 
} 

class First extends SayHello { 
    use SayWorldTrait; 
} 

class Second implements SayHelloInterface { 
    use SayWorldTrait; 
} 

try { 
    $test = new First(); 
    $test->sayHello(); // throws logic exception because the First class does not implements SayHelloInterface 
} catch(\Exception $e) { 
    echo $e->getMessage(); 
} 

try { 
    $test = new Second(); 
    $test->sayHello(); // throws logic exception because the Second class does not extends SayHello 
} catch(\Exception $e) { 
    echo $e->getMessage(); 
} 
1

La fase di compilazione PHP crea solo bytecode. Tutto il resto viene eseguito in fase di esecuzione, incluso il processo decisionale polimorfico. Così, il codice come di seguito compila:

class A {} 
class B extends A { 
    public function __construct() { 
     parent::__construct(); 
    } 
} 

ma soffia quando run:

$b = new B; 

Così si rigorosamente non possono avere parent controllo a tempo di compilazione. Il meglio che puoi fare è rimandarlo al runtime il prima possibile. Come hanno mostrato altre risposte, è possibile farlo all'interno del metodo tratto con instanceof. Personalmente preferisco usare il suggerimento del tipo quando so che un metodo tratto richiede un particolare contratto.

Tutto questo si riduce allo scopo fondamentale dei tratti: compilare copia e incolla del tempo. Questo è tutto. I tratti non sanno nulla dei contratti. Non sapere nulla del polimorfismo. Forniscono semplicemente un meccanismo per il riutilizzo. Se accoppiati con le interfacce, sono piuttosto potenti, anche se un po 'prolissi.

In realtà, il first academic discussion of traits mette a nudo l'idea che i tratti sono destinate ad essere "pura", nel senso che non hanno alcuna conoscenza dell'oggetto che li circonda:

Un tratto è essenzialmente un gruppo di metodi puri che funge da blocco per le classi ed è una primitiva unità di riutilizzo del codice. In questo modello, le classi sono composte da un insieme di tratti specificando il codice della colla che collega i tratti insieme e accede allo stato necessario.

"If Traits Weren't Evil, They'd Be Funny" riassume piacevolmente i punti, le insidie ​​e le opzioni. Non condivido il vetriolo dell'autore per i tratti: li uso quando ha senso e sempre in coppia con uno interface. Che l'esempio nella documentazione di PHP incoraggi un cattivo comportamento è sfortunato.

+1

Durante il tempo trascorso da quando ho fatto questa domanda, ho effettivamente smesso di usare i tratti. E cerco di riscrivere il mio vecchio codice ogni volta che è possibile per eliminare i tratti. Potresti dire che sono cresciuto. A volte sono utili ma solo in pochissime situazioni. –