На прошлой неделе я думал о создании нового типа классов. 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/