2010-07-28 14 views
18

Mi piace il modo in C#, dove è possibile scrivere un metodo di estensione, e poi fare cose come questa:Metodi di estensione C# -like in PHP?

string ourString = "hello"; 
ourString.MyExtension("another"); 

o anche

"hello".MyExtention("another"); 

C'è un modo per avere un comportamento simile in PHP?

+5

stringhe non sono oggetti in PHP. –

+2

Mi dispiace, che cos'è un "metodo di estensione"? come si differenzia dagli altri metodi? – Artefacto

+1

@Artefacto: "ciao" .MyExtention ("altro"); 'non lo ha spiegato? – caesay

risposta

9

Si potrebbe reimplementare tutte le stringhe come oggetti.

class MyString { 
    ... 
    function foo() { ... } 
} 

$str = new MyString('Bar'); 
$str->foo('baz'); 

Ma davvero non vorrebbe farlo. PHP semplicemente non è un linguaggio orientato agli oggetti al suo interno, le stringhe sono solo tipi primitivi e non hanno metodi.

La sintassi 'Bar'->foo('baz') è impossibile da raggiungere in PHP senza estendere il motore di base (che non è qualcosa che si desidera ottenere in entrambi, almeno non per questo scopo :)).


C'è anche niente di estendere la funzionalità di un oggetto che lo rende intrinsecamente meglio che scrivere semplicemente una nuova funzione che accetta un primitivo. In altre parole, il PHP equivalente a

"hello".MyExtention("another"); 

è

my_extension("hello", "another"); 

Per tutti intenzione e scopi ha la stessa funzionalità, basta sintassi diversa.

+10

Hell yes, i progettisti di PHP apprezzano molto l'approccio 'my_extension()' e qui [** the lovely result **] (http://www.php.net/manual/en/indexes.functions.php): – cvsguimaraes

+0

Buona fortuna concatenare molti metodi. In C# si può fare "test" .ToUpper(). ToLower.Split(). Join(). Trim(). Ora non mi preoccuperò nemmeno di scrivere il codice PHP per farlo. – Phiter

+0

@Phiter Questa è solo la differenza tra lingue OO e lingue non OO. Soprattutto nelle lingue non OO lo scrivi "inside-out" ('trim (join (...))') invece che in modalità concatenata. Non sto commentando ciò che è meglio o peggio, è solo così com'è. – deceze

4

Da PHP 5.4 ci sono traits che possono essere utilizzati come metodi di estensione.

Esempio:

<?php 
trait HelloWorld { 
    public function sayHelloWorld() { 
     echo 'Hello World'; 
    } 
} 

class MyHelloWorld { 
    use HelloWorld; 
    public function sayExclamationMark() { 
     echo '!'; 
    } 
} 

$o = new MyHelloWorld(); 
$o->sayHelloWorld(); 
$o->sayExclamationMark(); 
?> 

risultato:

Hello World! 

Una volta che si include un tratto a una classe, consente di chiamare, ad esempio con un nome Extension, è possibile aggiungere qualsiasi metodo che si desidera e di individuare loro altrove. Quindi in questo esempio lo use Extension diventa una decorazione una tantum per le classi estendibili.

+13

I tratti non sono uguali ai metodi di estensione perché richiedono ancora la modifica della classe originale. – zzzzBov

+0

Sì ma una volta che includi un tratto, chiamalo con un nome "Estensione", puoi aggiungere qualsiasi metodo che desideri e localizzarlo altrove. Quindi "usa TraitName" diventa solo una volta decorazione. –

4

Allontanandosi dalla questione di primitive non-oggetto in PHP, quando si tratta di lezioni effettive in PHP, se l'ambiente è sano di mente, è probabile che si può decorare una data classe di ordinare-of § estensione emulare metodi.

Dato un interfaccia e implementazione:

interface FooInterface { 
    function sayHello(); 
    function sayWorld(); 
} 

class Foo implements FooInterface { 
    public function sayHello() { 
     echo 'Hello'; 
    } 
    public function sayWorld() { 
     echo 'World'; 
    } 
} 

Finché eventuali dipendenze da Foo sono in realtà dipende l'interfaccia FooInterface (questo è ciò che intendo quando parlo sano) è possibile implementare FooInterface te stesso come un wrapper a Foo, chiamate l'ora di Foo quando necessario, e aggiungere ulteriori metodi di cui hai bisogno:

class DecoratedFoo implements FooInterface { 
    private $foo; 
    public function __construct(FooInterface $foo) { 
     $this->foo = $foo; 
    } 
    public function sayHello() { 
     $this->foo->sayHello(); 
    } 
    public function sayWorld() { 
     $this->foo->sayWorld(); 
    } 
    public function sayDanke() { 
     echo 'Danke'; 
    } 
    public function sayShoen() { 
     echo 'Shoen'; 
    } 
} 

I metodi sayHello() e sayWorld() vengono corretti per l'oggetto contenente , tuttavia abbiamo aggiunto sayDanke() e sayShoen().

la seguente:

function acceptsFooInterface(FooInterface $foo) { 
    $foo->sayHello(); 
    $foo->sayWorld(); 
} 

$foo = new Foo(); 
acceptsFooInterface($foo); 

funziona come previsto, cedendo HelloWorld; ma lo fa in questo modo:

$decoratedFoo = new DecoratedFoo($foo); 
acceptsFooInterface($decoratedFoo); 

$decoratedFoo->sayDanke(); 
$decoratedFoo->sayShoen(); 

che si traduce in HelloWorldDankeShoen.

Questo è un uso limitato del potenziale nel modello decoratore; è possibile modificare il comportamento dei metodi implementati, o semplicemente non inoltrarli affatto (tuttavia, vogliamo mantenere il comportamento previsto dalla definizione di classe originale in questo esempio)

È questo uno a uno soluzione per l'implementazione di metodi di estensione (come da C#) in PHP? No, decisamente no; ma l'estensibilità fornita da questo approccio aiuterà a risolvere i problemi in modo più approssimativo.


§ pensato che avrei elaboro sulla base di alcuni conversazione in chat del soggetto: si è mai andare a replicare (non oggi, e probabilmente non domani) in PHP, ma la chiave nella la mia risposta è modelli di progettazione. Offrono l'opportunità di portare le strategie da una lingua all'altra quando non è possibile necessariamente (in genere o mai).

+1

+1 per la programmazione su interfacce. – rdlowrey

2

Ho un'altra implementazione per questo in PHP> = 5.3.0 ed è come un decoratore che Northborn Design ha spiegato.

Tutto ciò di cui abbiamo bisogno è un'API per creare estensioni e un decoratore per applicare le estensioni.

Dobbiamo ricordare che nei metodi di estensione C# non interrompono l'incapsulamento dell'oggetto esteso e non modificano l'oggetto (non serve a tal fine, implementare il metodo sarebbe più efficace). E i metodi estensioni sono puri statiche e ricevono l'istanza dell'oggetto, come nell'esempio che segue (C#, from MSDN):

public static int WordCount(this String str) 
{ 
    return str.Split(new char[] { ' ', '.', '?' }, 
        StringSplitOptions.RemoveEmptyEntries).Length; 
} 

Naturalmente non abbiamo oggetti String in PHP, ma per tutti gli altri oggetti possiamo creare decoratori generici per quel tipo di magia.

permette di vedere la mia applicazione per questo:

L'API:

<?php 

namespace Pattern\Extension; 

/** 
* API for extension methods in PHP (like C#). 
*/ 
class Extension 
{ 
    /** 
    * Apply extension to an instance. 
    * 
    * @param object $instance 
    * @return \Pattern\Extension\ExtensionWrapper 
    */ 
    public function __invoke($instance) 
    { 
     return Extension::apply($instance); 
    } 

    /** 
    * Apply extension to an instance. 
    * 
    * @param object $instance 
    * @return \Pattern\Extension\ExtensionWrapper 
    */ 
    public static function apply($instance) 
    { 
     return new ExtensionWrapper($instance, \get_called_class()); 
    } 

    /** 
    * @param mixed $instance 
    * @return boolean 
    */ 
    public static function isExtensible($instance) 
    { 
     return ($instance instanceof Extensible); 
    } 
} 
?> 

il decoratore:

<?php 

namespace Pattern\Extension; 

/** 
* Demarcate decorators that resolve the extension. 
*/ 
interface Extensible 
{ 
    /** 
    * Verify the instance of the holded object. 
    * 
    * @param string $className 
    * @return bool true if the instance is of the type $className, false otherwise. 
    */ 
    public function holdsInstanceOf($className); 

    /** 
    * Returns the wrapped object. 
    * If the wrapped object is a Extensible the returns the unwrap of it and so on. 
    * 
    * @return mixed 
    */ 
    public function unwrap(); 

    /** 
    * Magic method for the extension methods. 
    * 
    * @param string $name 
    * @param array $args 
    * @return mixed 
    */ 
    public function __call($name, array $args); 
} 
?> 

E l'implementazione generica:

<?php 

namespace Pattern\Extension; 

/** 
* Generic version for the Extensible Interface. 
*/ 
final class ExtensionWrapper implements Extensible 
{ 
    /** 
    * @var mixed 
    */ 
    private $that; 

    /** 
    * @var Extension 
    */ 
    private $extension; 

    /** 
    * @param object $instance 
    * @param string | Extension $extensionClass 
    * @throws \InvalidArgumentException 
    */ 
    public function __construct($instance, $extensionClass) 
    { 
     if (!\is_object($instance)) { 
      throw new \InvalidArgumentException('ExtensionWrapper works only with objects.'); 
     } 

     $this->that = $instance; 
     $this->extension = $extensionClass; 
    } 

    /** 
    * {@inheritDoc} 
    * @see \Pattern\Extension\Extensible::__call() 
    */ 
    public function __call($name, array $args) 
    { 
     $call = null; 
     if (\method_exists($this->extension, '_'.$name)) { 
      // this is for abstract default interface implementation 
      \array_unshift($args, $this->unwrap()); 
      $call = array($this->extension, '_'.$name); 
     } elseif (\method_exists($this->extension, $name)) { 
      // this is for real implementations 
      \array_unshift($args, $this->unwrap()); 
      $call = array($this->extension, $name); 
     } else { 
      // this is for real call on object 
      $call = array($this->that, $name); 
     } 
     return \call_user_func_array($call, $args); 
    } 

    /** 
    * {@inheritDoc} 
    * @see \Pattern\Extension\Extensible::unwrap() 
    */ 
    public function unwrap() 
    { 
     return (Extension::isExtensible($this->that) ? $this->that->unwrap() : $this->that); 
    } 

    /** 
    * {@inheritDoc} 
    * @see \Pattern\Extension\Extensible::holdsInstanceof() 
    */ 
    public function holdsInstanceOf($className) 
    { 
     return \is_a($this->unwrap(), $className); 
    } 
} 
?> 

L'uso:

assumere una classe di terze parti esiste:

class ThirdPartyHello 
{ 
    public function sayHello() 
    { 
     return "Hello"; 
    } 
} 

Crea proprio interno:

use Pattern\Extension\Extension; 

class HelloWorldExtension extends Extension 
{ 
    public static function sayHelloWorld(ThirdPartyHello $that) 
    { 
     return $that->sayHello().' World!'; 
    } 
} 

Plus: Per interfaccia Lovers, creare un'estensione Abstract:

<?php 
interface HelloInterfaceExtension 
{ 
    public function sayHelloFromInterface(); 
} 
?> 
<?php 
use Pattern\Extension\Extension; 

abstract class AbstractHelloExtension extends Extension implements HelloInterfaceExtension 
{ 
    public static function _sayHelloFromInterface(ThirdPartyOrLegacyClass $that) 
    { 
     return $that->sayHello(). ' from Hello Interface'; 
    } 
} 
?> 

Poi usarlo :

//////////////////////////// 
// You can hide this snippet in a Dependency Injection method 

$thatClass = new ThirdPartyHello(); 

/** @var ThirdPartyHello|HelloWorldExtension $extension */ 
$extension = HelloWorldExtension::apply($thatClass); 

////////////////////////////////////////// 

$extension->sayHello(); // returns 'Hello' 
$extension->sayHelloWorld(); // returns 'Hello World!' 

////////////////////////////////////////// 
// Abstract extension 

$thatClass = new ThirdPartyHello(); 

/** @var ThirdPartyHello|HelloInterfaceExtension $extension */ 
$extension = AbstractHelloExtension::apply($instance); 

$extension->sayHello(); // returns 'Hello' 
$extension->sayHelloFromInterface(); // returns 'Hello from Hello Interface' 

Pro: modo

  • molto simili di C# i metodi di estensione in PHP;
  • Impossibile testare direttamente un'istanza di estensione come un'istanza dell'oggetto esteso, ma ciò è utile perché è più sicuro perché possiamo avere luoghi in cui l'istanza di tale classe non è estesa;
  • Come l'intento di un framework per migliorare l'agilità del team, è necessario scrivere meno;
  • L'uso dell'estensione sembra essere parte dell'oggetto ma è appena decorato (forse è interessante per i team sviluppare rapidamente ma rivedere in futuro l'implementazione di tale oggetto esteso se è coinvolta la legacy);
  • È possibile utilizzare il metodo statico dell'estensione direttamente per migliorare le prestazioni, ma facendo in modo che si perda la capacità di deridere parti del codice (DI è altamente indicato).

Contro:

  • L'estensione devono essere dichiarate per l'oggetto, la sua non è solo come importazione come in C#, è necessario "decorare" l'istanza desiderata per dare l'estensione ad esso.
  • Impossibile testare direttamente un'istanza di estensione come un'istanza dell'oggetto esteso, più codice per testare utilizzando l'API;
  • Svantaggi delle prestazioni a causa dell'uso di metodi magici (ma quando sono necessarie prestazioni cambiamo linguaggio, ricreamo il nucleo, utilizzo di strutture minimaliste, assemblatore se necessario);

Ecco un Gist per quella Api: https://gist.github.com/tennaito/9ab4331a4b837f836ccdee78ba58dff8

Problemi correlati