czwartek, 31 stycznia 2013

Refleksje - rzutowanie obiektu do klasy

Java pozwala na tytułową operacją w bardzo prosty sposób
Object1.cast( Object2 );
Object1 jest rzutowany do klasy reprezentowanej przez Object2. Ostatnio naszła mnie ochota na coś podobnego w PHP, żeby rzutować obiekt klasy rozszerzający rodzica do innej klasy rozszerzającej rodzica. W PHP nie ma na to prostej metody, ale można ją samemu napisać bardzo elegancko
// rozszerzenie klasy jest jak najbardziej na miejscu bo będziemy pracować na jej instancji
class ReflectionObjectExtended extends ReflectionObject
 {
 
 private $Obj;
 
 public function __construct( $Object )
  {
  
  parent::__construct( $Object );
  
  $this->Obj = $Object; // przyda też się oryginalny obiekt
  
  }

 // nowa metoda jako argumenty przyjmuje refleksję klasy do której obiekt ma być rzutowany, oraz tablicę z argumentami dla konstruktora
 public function cast( ReflectionClass $ReflectionClass, array $arg = array() )
  {
  
  $New = $ReflectionClass->newInstanceArgs( $arg ); // tworzymy instancję obiektu który zostanie zwrócony
  
  foreach( $this->getProperties() as $ReflectionProperty ) // pobieramy refleksje parametrów aktualnego obiektu
   {
    
   $ReflectionProperty->setAccessible( TRUE ); // dzięki temu mamy dostęp do chronionych i prywatnych właściwości
   
   $name = $ReflectionProperty->getName(); // pobieramy nazwę...
   $value = $ReflectionProperty->getValue( $this->Obj ); // ...i wartość aktualnego obiektu

   if( $ReflectionClass->hasProperty( $name ) ) // jeśli klasa do której rzutujemy ma taką właściwość...
    {
    $Property = $ReflectionClass->getProperty( $name ); //...bierzemy jej refleksję...
    $Property->setAccessible( TRUE ); // ...udostępniamy j.w...
    $Property->setValue( $New, $value ); // ...i przypisujemy nową wartość...
    }
   else // ...w przeciwnym razie...
    {
    $New->$name = $value; // ...tworzymy właściwość
    }
    
   }
  
  return $New;
  
  }
 
 }
A oto zastosowanie
$DOMDocument = new DOMDocument();
$ReflectionObjectExtended = new ReflectionObjectExtended( $DOMDocument );
$ReflectionObjectExtended->cast( new ReflectionClass( 'DOMElement' ), array( 'strong' ) );
Bardziej praktycznym przykładem jest przeniesienie danych z obiektu dla użytkownika niezalogowanego do obiektu dla zalogowanego podczas logowania.

wtorek, 29 stycznia 2013

Refleksje - uzyskanie tablicy argumentów

Jakiś czas temu spotkałem się w jakimś języku programowania z "agregacją" argumentów, nie pamiętam gdzie, ale wyglądało to mniej więcej tak:

function test( foo, bar ... )
    {

    foo; // 1

    bar; // [ 2, 3, 4, 5 ]

    }

test( 1, 2, 3, 4, 5 );
foo jest to typowy argument - nazwany, bar to wspomniany agregujący, który przechowuje wartości w tablicy. W PHP podobna sytuacja jest rozwiązana specjalnym zestawem funkcji:
function test( $foo )
    {

    $foo; // 1
    func_num_args(); // 5
    func_get_args(); // array( 1, 2, 3, 4, 5 ) 
    func_get_arg( 1 ); // 2

    }

test(1,2,3,4,5);
Jak widać func_get_args() zwraca tablicę indeksowaną numerycznie, a co jeśli chciałbym mieć w kluczach nazwy argumentów? Tutaj właśnie pomoże programowanie refleksyjne
function test( $foo )
    {

    $foo; // 1

    $ReflectionFunction = new ReflectionFunction( __FUNCTION__ ); // 1. tworzona jest refleksja aktualnej funkcji
    $param = $ReflectionFunction->getParameters();                // 2. z refleksji funkcji pobierana jest tablica refleksji parametrów
    $count = count( $param );                                     // 3. zapisuję liczbę parametrów
    $bar = array_combine(                                         // 8. wartości połączonej tablicy służą jako klucze dla kolejnych argumentów
        array_merge(                                              // 7. łączone są obie tablice
            array_map(                                            // 4. tablica parametrów jest mapowana
                function( $ReflectionParameter )
                    {
                    return $ReflectionParameter->name;            // 5. z parametru wyciągana jest tylko jej nazwa
                    },
                 $param ),
            range( $count, func_num_args() - $count ) ),          // 6. tworzona jest tablica z przyszłymi kluczami dla nienazwanych parametrów
        func_get_args() );

    $bar; // array( 'foo' => 1, 1 => 2, 2=> 3, 3 => 4, 4 => 5 ) 

    }

test( 1, 2, 3, 4, 5 );
Jest to złożone rozwiązanie, ale jednocześnie uniwersalne (niezależne od liczby argumentów nazwanych i nienazwanych), które mi się przydało.

Update 2013-01-31 23:00:
W dyskusji z bratem (który w przeciwieństwie do mnie jest na bieżąco z C++) wyszło że w C/C++ jest "agregacja" argumentów, nazywana zmienną liczbą argumentów
void fun( char *msg, ... );