Статьи

Классы выполнения. Эксперимент с PHP и объектно-ориентированным программированием

На прошлой неделе я думал о создании нового типа классов. PHP классы, но создаются динамически во время выполнения. Когда мне в голову пришла эта идея, я прочитал следующую статью и хотел написать что-то подобное. Предупреждение: возможно, это что-то совершенно бесполезное, но я хотел создать работающий прототип (и было весело делать это ;)). Давайте начнем:

Мы собираемся создать что-то вроде этого:

class Human
{
  private $name;
  function __construct($name)
  {
    $this->name = $name;
  }

  function hello()
  {
    return "{$this->name} says hello";
  }
}

$gonzalo = new Human('Gonzalo');
$peter = new Human('Peter');

echo $gonzalo->hello(); // outputs: Gonzalo says hello
echo $peter->hello(); // outputs: Peter says hello

но без использования оператора класса, динамически добавляя конструктор и метод hello.

function testSimpleUsage()
{
  $human = HClass::define()
    ->fct(HClass::__construct, function($self, $name) {$self->name = $name;})
    ->fct('hello', function($self) {
        return "{$self->name} says hello";
      });

  $gonzalo = $human->create('Gonzalo');
  $peter = $human->create('Peter');

  $this->assertEquals("Gonzalo says hello", $gonzalo->hello());
  $this->assertEquals("Peter says hello", $peter->hello());
}

Я также хочу протестировать неопределенные функции за исключением:

function testCallingUndefinedFunctions()
{
  $human = HClass::define()
    ->fct(HClass::__construct, function($self, $name) {$self->name = $name;})
    ->fct('hello', function($self) {
            return "{$self->name} says hello";
          });

  $gonzalo = $human->create('Gonzalo');
  $this->setExpectedException('Exception', "ERROR Method 'goodbye' does not exits");
  $gonzalo->goodbye();
}

И простое наследование тоже

function testInheritance()
{
  $human = HClass::define()
    ->fct(HClass::__construct, function($self, $name) {$self->name = $name;})
    ->fct('hello', function($self) {
            return "{$self->name} says hello";
          });

  $shyHuman = HClass::define($human)
    ->fct('hello', function($self) {
            return "{$self->name} is shy and don't says hello";
          });

  $gonzalo = $human->create('Gonzalo');
  $peter = $shyHuman->create('Peter');

  $this->assertEquals("Gonzalo says hello", $gonzalo->hello());
  $this->assertEquals("Peter is shy and don't says hello", $peter->hello());
}

Теперь мы собираемся создать динамически функции:

function testDinamicallyFunctionCreation()
{
  $human = HClass::define()
    ->fct(HClass::__construct, function($self, $name) {$self->name = $name;})
    ->fct('hello', function($self) {
            return "{$self->name} says hello";
          });

  $gonzalo = $human->create('Gonzalo');
  $this->assertEquals("Gonzalo says hello", $gonzalo->hello());

  try {
    $gonzalo->goodbye();
  } catch (Exception $e) {
    $this->assertEquals("ERROR Method 'goodbye' does not exits", $e->getMessage());
  }

  $human->fct('goodbye', function($self) {
            return "{$self->name} says goodbye";
          });

  $this->assertEquals("Gonzalo says goodbye", $gonzalo->goodbye());
}

Вот и все. Оно работает. вероятно, в PHP5.4 мы можем удалить переменную «$ self». Передать реальный экземпляр класса обратному вызову — уродливый трюк, благодаря функции «Добавлено закрытие $ this support back», добавленной в новой версии PHP. Но по крайней мере сейчас нам это нужно.

А теперь еще один поворот винт. Давайте попробуем создать ката FizzBuzz с этими «классами времени выполнения». Я создам две версии FizzBuzz. Здесь вы можете увидеть мою реализацию обеих версий со «стандартным» PHP. С простым классом, и еще один с двумя классами и Dependency Injection. Теперь с помощью эксперимента:

function testFizzBuzz()
{
  $fizzBuzz = HClass::define()
    ->fct('run', function($self, $elems = 100) {
        list($fizz, $buzz) = array('Fizz', 'Buzz');
        return array_map(function ($element) use ($fizz, $buzz) {
            $out = array();
            if ($element % 3 == 0 || strpos((string) $element, '3') !== false ) {
              $out[] = $fizz;
            }
            if ($element % 5 == 0 || strpos((string) $element, '5') !== false ) {
              $out[] = $buzz;
            }
            return (count($out) > 0) ? implode('', $out) : $element;
          }, range(0, $elems-1));
      });
  $fizzBuzz = $fizzBuzz->create();
  $arr = $fizzBuzz->run();

  $this->assertEquals(count($arr), 100);
  $this->assertEquals($arr[1],  1);
  $this->assertEquals($arr[3],  'Fizz');
  $this->assertEquals($arr[4],  4);
  $this->assertEquals($arr[5],  'Buzz');
  $this->assertEquals($arr[6],  'Fizz');
  $this->assertEquals($arr[20], 'Buzz');
  $this->assertEquals($arr[13], 'Fizz');
  $this->assertEquals($arr[15], 'FizzBuzz');
  $this->assertEquals($arr[53], 'FizzBuzz');
}

 

function testAnotherFizzBuzzImplementationWithDependencyInjection()
{
  $fizzBuzz = HClass::define();
  $fizzBuzz->fct(HClass::__construct, function($self, $fizzBuzzElement) {
       $self->fizzBuzzElement = $fizzBuzzElement;
     });
  $fizzBuzz->fct('run', function($self, $elems = 100) {
       $out = array();
       foreach (range(1, $elems) as $elem) {
         $out[$elem] =  $self->fizzBuzzElement->render($elem);
       }
       return $out;
     });

  $fizzBuzzElement = HClass::define()
    ->fct('render', function($self, $element) {
        list($fizz, $buzz) = array('Fizz', 'Buzz');
        $out = array();
        if ($element % 3 == 0 || strpos((string) $element, '3') !== false ) {
          $out[] = $fizz;
        }

        if ($element % 5 == 0 || strpos((string) $element, '5') !== false ) {
          $out[] = $buzz;
        }
        return (count($out) > 0) ? implode('', $out) : $element;
    });

  $fbe = $fizzBuzzElement->create();

  $this->assertEquals($fbe->render(1),  1);

  $this->assertEquals($fbe->render(2),  2);
  $this->assertEquals($fbe->render(3),  'Fizz');
  $this->assertEquals($fbe->render(4),  4);
  $this->assertEquals($fbe->render(5),  'Buzz');
  $this->assertEquals($fbe->render(6),  'Fizz');
  $this->assertEquals($fbe->render(20), 'Buzz');
  $this->assertEquals($fbe->render(13), 'Fizz');
  $this->assertEquals($fbe->render(15), 'FizzBuzz');
  $this->assertEquals($fbe->render(53), 'FizzBuzz');

  $fb = $fizzBuzz->create($fbe);
  $arr = $fb->run();

  $this->assertEquals(count($arr), 100);
  $this->assertEquals($arr[1],  1);
  $this->assertEquals($arr[3],  'Fizz');
  $this->assertEquals($arr[4],  4);
  $this->assertEquals($arr[5],  'Buzz');
  $this->assertEquals($arr[6],  'Fizz');
  $this->assertEquals($arr[20], 'Buzz');
  $this->assertEquals($arr[13], 'Fizz');
  $this->assertEquals($arr[15], 'FizzBuzz');
  $this->assertEquals($arr[53], 'FizzBuzz');
}

И это все. Как я уже говорил, вероятно, эти «гибридные классы» или «классы времени выполнения» (я не знаю, как их назвать) совершенно бесполезны, но это интересно.

phpunit HclassTest.php
PHPUnit 3.4.5 by Sebastian Bergmann.

......

Time: 0 seconds, Memory: 5.00Mb

OK (6 tests, 39 assertions)

Полный код на github

От http://gonzalo123.wordpress.com/2011/08/08/runtime-classes-a-experiment-with-php-and-object-oriented-programming/