Сейчас 1:00, крайний срок доставки вашего веб-приложения — 8 часов … и он не работает. Когда вы пытаетесь выяснить, что происходит, вы заполняете свой код var_dump()
и die()
везде, чтобы увидеть, где ошибка …
Вы раздражены. Каждый раз, когда вы хотите проверить возвращаемое значение или присвоение переменной, вы должны изменить свой исходный код, выполнить свое приложение и посмотреть результаты … В конце вы не уверены, удалили ли вы все эти var_dumps из код. Вам знакома эта ситуация?
PsySH на помощь
PsySH — это цикл чтения-проверки-печати (или REPL ). Возможно, вы уже использовали REPL через консоль javascript вашего браузера. Если у вас есть, вы знаете, что он обладает большой мощностью и может быть полезен при отладке кода JS.
Говоря о PHP, вы могли раньше использовать интерактивную консоль php -a
( php -a
). Там вы можете написать некоторый код, и консоль выполнит его, как только вы нажмете Enter:
php -a Interactive shell php > $a = 'Hello world!'; php > echo $a; Hello world! php >
К сожалению, интерактивная оболочка не является REPL, так как в ней отсутствует буква «P» (печать). Мне пришлось выполнить оператор echo
чтобы увидеть содержимое $a
. В истинном REPL мы бы увидели его сразу после присвоения ему значения.
Вы можете установить PsySH глобально либо с помощью composer g require
, либо загрузить исполняемый файл PsySH:
Композитор
composer g require psy/psysh:~0.1 psysh
Прямая загрузка (Linux / Mac)
wget psysh.org/psysh chmod +x psysh ./psysh
Кроме того, вы можете включить его для каждого проекта с помощью composer, как вы увидите позже в этой статье.
Теперь давайте немного поиграем с PsySH.
./psysh Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman >>>
Главной помощью станет ваш лучший друг. Это то, что даст вам всевозможные команды и их объяснения:
>>> help help Show a list of commands. Type `help [foo]` for information about [foo]. Aliases: ? ls List local, instance or class variables, methods and constants. Aliases: list, dir dump Dump an object or primitive. doc Read the documentation for an object, class, constant, method or property. Aliases: rtfm, man show Show the code for an object, class, constant, method or property. wtf Show the backtrace of the most recent exception. Aliases: last-exception, wtf? trace Show the current call stack. buffer Show (or clear) the contents of the code input buffer. Aliases: buf clear Clear the Psy Shell screen. history Show the Psy Shell history. exit End the current session and return to caller. Aliases: quit, q
>>> help ls Usage: ls [--vars] [-c|--constants] [-f|--functions] [-k|--classes] [-I|--interfaces] [-t|--traits] [-p|--properties] [-m|--methods] [-G|--grep="..."] [-i|--insensitive] [-v|--invert] [-g|--globals] [-n|--internal] [-u|--user] [-C|-- category="..."] [-a|--all] [-l|--long] [target] Aliases: list, dir Arguments: target A target class or object to list. Options: --vars Display variables. --constants (-c) Display defined constants. --functions (-f) Display defined functions. --classes (-k) Display declared classes. --interfaces (-I) Display declared interfaces. --traits (-t) Display declared traits. --properties (-p) Display class or object properties (public properties by default). --methods (-m) Display class or object methods (public methods by default). --grep (-G) Limit to items matching the given pattern (string or regex). --insensitive (-i) Case-insensitive search (requires --grep). --invert (-v) Inverted search (requires --grep). --globals (-g) Include global variables. --internal (-n) Limit to internal functions and classes. --user (-u) Limit to user-defined constants, functions and classes. --category (-C) Limit to constants in a specific category (eg "date"). --all (-a) Include private and protected methods and properties. --long (-l) List in long format: includes class names and method signatures. Help: List variables, constants, classes, interfaces, traits, functions, methods, and properties. Called without options, this will return a list of variables currently in scope. If a target object is provided, list properties, constants and methods of that target. If a class, interface or trait name is passed instead, list constants and methods on that class. eg >>> ls >>> ls $foo >>> ls -k --grep mongo -i >>> ls -al ReflectionClass >>> ls --constants --category date >>> ls -l --functions --grep /^array_.*/ >>>
В основном, что может сделать REPL:
>>> $a = 'hello'; => "hello" >>>
Обратите внимание, что если мы сравниваем PsySH с интерактивной консолью PHP, PsySH печатает значение $a
как только оно назначено.
Более сложный пример может быть следующим:
>>> function say($a) { ... echo $a; ... } => null >>> say('hello'); hello => null >>>
Я определил функцию say()
и вызвал ее. Эти два null
вы видите потому, что ни определение функции, ни ее выполнение не вернули значение (функция отображает значение). Кроме того, при определении функции приглашение изменилось с >>>
на ...
Можем ли мы определить класс и создать его экземпляр?
>>> class Foo ... { ... protected $a; ... ... public function setA($a) { ... $this->a = $a; ... } ... ... public function getA() { ... return $this->a; ... } ... } => null >>> $foo = new Foo(); => <Foo #000000001dce50dd000000002dda326e> {} >>> $foo->setA('hello'); => null >>> $foo->getA(); => "hello" >>>
Когда я создал Foo, конструктор вернул ссылку на объект. Вот почему PsySh напечатал <Foo #000000001dce50dd000000002dda326e>
. Теперь посмотрим, что интересного в PsySH и объектах.
>>> ls $foo Class Methods: getA, setA >>>
Если вы случайно забыли, какие методы определил класс Foo, теперь у вас есть ответ. Вы использовали интерфейс командной строки ОС Linux или Mac? Тогда вы можете быть знакомы с командой ls
. Помните опции -la
?
>>> ls -la $foo Class Properties: $a "hello" Class Methods: getA public function getA() setA public function setA($a)
Сладкий, не правда ли?
Истинная сила PsySH проявляется при интеграции с веб-приложением, поэтому давайте создадим его.
Демо приложение
Я собираюсь реализовать быстрое приложение для демонстрации шаблона дизайна декоратора . Диаграмма классов UML такого шаблона выглядит следующим образом:
Не беспокойтесь, если вы не очень много знаете о UML или шаблонах проектирования , для их понимания не требуется этой статьи.
Также для этого проекта я создал набор тестовых случаев. Эти тесты могут быть запущены с помощью phpUnit . Опять же, вы не должны быть знакомы с модульным тестированием, чтобы понять эту статью.
Полный исходный код этого маленького приложения можно найти по адресу https://github.com/sitepoint-examples/PsySH.
Прежде всего, давайте определим наш файл composer.json
, чтобы объявить зависимость от PsySH :
{ "name": "example/psysh", "authors": [ { "name": "John Doe", "email": "[email protected]" } ], "require": { "psy/psysh": "~0.1" }, "autoload": { "psr-4": {"Acme\\": "src/"} } }
После composer install
вас все получится.
Пожалуйста, взгляните на следующий исходный код из файла public/decorator.php
. Он будет создавать экземпляры объектов SimpleWindow, DecoratedWindow и TitledWindow для демонстрации шаблона декоратора:
<?php chdir(dirname(__DIR__)); require_once('vendor/autoload.php'); use Acme\Patterns\Decorator\SimpleWindow; use Acme\Patterns\Decorator\DecoratedWindow; use Acme\Patterns\Decorator\TitledWindow; echo PHP_EOL . 'Simple Window' . PHP_EOL; $window = new SimpleWindow(); echo $window->render(); echo PHP_EOL . 'Decorated Simple Window' . PHP_EOL; $decoratedWindow = new DecoratedWindow($window); echo $decoratedWindow->render(); echo PHP_EOL . 'Titled Simple Window' . PHP_EOL; $titledWindow = new TitledWindow($window); echo $titledWindow->render();
Мы можем выполнить код через CLI PHP (интерфейс командной строки) или через веб-сервер, если он настроен. Мы также можем использовать внутренний веб-сервер PHP.
Отладка в кли
Выполнение приведенного выше кода через интерфейс командной строки будет выглядеть так:
php public/decorator.php Simple Window +-------------+ | | | | | | | | | | +-------------+ Decorated Simple Window +-------------+ | | | | | | | | | | +-------------+ Titled Simple Window +-------------+ |Title | +-------------+ | | | | | | | | | | +-------------+
Как мы можем взаимодействовать с PsySH? Просто добавьте \Psy\Shell::debug(get_defined_vars());
в любом месте кода, где вы хотите отлаживать приложение, обычно там, где вы вставляете оператор var_dump()
:
<?php chdir(dirname(__DIR__)); require_once('vendor/autoload.php'); //... a lot of code here $titledWindow = new TitledWindow($window); echo $titledWindow->render(); \Psy\Shell::debug(get_defined_vars()); //we want to debug our application here!
После сохранения файла мы получим следующий вывод:
php public/decorator.php Simple Window +-------------+ | | | | | | | | | | +-------------+ Decorated Simple Window +-------------+ | | | | | | | | | | +-------------+ Titled Simple Window +-------------+ |Title | +-------------+ | | | | | | | | | | +-------------+ Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman >>>
Выполнение скрипта будет приостановлено, и теперь у нас есть подсказка от PsySH. Я передаю get_defined_vars()
в качестве параметра в Psy\Shell::debug()
поэтому у меня есть доступ ко всем определенным переменным внутри оболочки:
>>> ls Variables: $_COOKIE, $_FILES, $_GET, $_POST, $_SERVER, $argc, $argv, $decoratedWindow, $titledWindow, $window >>>
Давайте рассмотрим переменную $window
:
>>> ls -al $window Class Methods: render public function render() >>>
Приятно иметь в приложении PsySH то, что мы можем исследовать исходный код экземпляра объекта.
>>> show $window class Acme\Patterns\Decorator\SimpleWindow implements Acme\Patterns\Decorator\Window class SimpleWindow implements Window { public function render() { $returnString = <<<EOL +-------------+ | | | | | | | | | | +-------------+ EOL; return $returnString . PHP_EOL; } } >>>
Итак, $window
— это экземпляр SimpleWindow, который реализует интерфейс Window … Интересно, как выглядит исходный код интерфейса Window …
>>> show Acme\Patterns\Decorator\Window interface Acme\Patterns\Decorator\Window interface Window { public function render(); } >>>
Почему SimpleWindow и DecoratedWindow имеют одинаковый вывод? Давайте рассмотрим объект $decoratedWindow
.
>>> ls -al $decoratedWindow Class Properties: $windowReference <Digitec\Patterns\Decorator\SimpleWindow #000000003643d67700000000731101b7> Class Methods: __construct public function __construct(Digitec\Patterns\Decorator\Window $windowReference) getWindowReference public function getWindowReference() render public function render() setWindowReference public function setWindowReference(Digitec\Patterns\Decorator\Window $windowReference) >>>
Этот объект «тяжелее», чем объект SimpleWindow, поэтому исходный код может быть длинным… Давайте рассмотрим исходный код только для метода render()
:
>>> show $decoratedWindow->render public function render() public function render() { return $this->getWindowReference()->render(); }
Метод getWindowReference()
вызывается, а затем возвращает результат из метода render()
. Давайте проверим источник getWindowReference()
:
>>> show $decoratedWindow->getWindowReference public function getWindowReference() public function getWindowReference() { return $this->windowReference; } >>>
Этот метод возвращает свойство объекта windowReference, и, как мы видели из команды ls -al
выше, это экземпляр Acme\Patterns\Decorator\SimpleWindow
. Конечно, мы могли бы просто посмотреть, как работает DecoratedWindow::__construct()
, но это еще один способ проверки.
Отладка со встроенным сервером
К сожалению, отладка через веб-сервер, такой как Apache, не поддерживается. Однако мы можем отлаживать наше приложение, используя встроенный сервер PHP:
$ cd public $ php -S localhost:8080 PHP 5.5.8 Development Server started at Wed Jul 23 17:40:30 2014 Listening on http://localhost:8080 Document root is /home/action/workspace/lab/PsySH/public Press Ctrl-C to quit.
Сервер разработки теперь прослушивает соединения через порт 8080, поэтому, как только мы decorator.php
файл decorator.php
через этот веб-сервер ( http: // localhost: 8080 / decorator.php ), мы должны увидеть следующее:
Psy Shell v0.1.11 (PHP 5.5.8 — cli-server) by Justin Hileman >>>
Мы можем начать играть с PsySH так же, как и с CLI
>>> ls -al Variables: $_COOKIE [] $_FILES [] $_GET [] $_POST [] $_SERVER Array(19) $decoratedWindow <Acme\Patterns\Decorator\DecoratedWindow #0000000031ef2e3e000000003c2d3a90> $titledWindow <Acme\Patterns\Decorator\TitledWindow #0000000031ef2e39000000003c2d3a90> $window <Acme\Patterns\Decorator\SimpleWindow #0000000031ef2e3f000000003c2d3a90> $_ null >>> exit Exit: Goodbye.
Отладка с помощью юнит-тестов
Как хороший разработчик, вы должны написать модульные тесты для своего кода в качестве доказательства того, что он работает как положено. В файлах проекта вы найдете папку tests
, и если у вас установлен phpUnit, вы можете запустить тесты внутри нее.
cd tests/ phpunit PHPUnit 4.0.14 by Sebastian Bergmann. Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml ...F+-------------+ |Title | Time: 66 ms, Memory: 4.50Mb There was 1 failure: 1) AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle Failed asserting that true is false. /home/action/workspace/lab/PsySH/tests/Patterns/Decorator/TitledWindowTest.php:46 FAILURES! Tests: 4, Assertions: 7, Failures: 1.
Несмотря на то, что код работает без сбоев, тест не пройден. Мы можем исследовать дальше, запустив только провальный тест:
$ phpunit --verbose --debug --filter=TitledWindowTest::testAddTitle PHPUnit 4.0.14 by Sebastian Bergmann. Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml Starting test 'AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle'. F+-------------+ |Title | Time: 60 ms, Memory: 4.25Mb There was 1 failure: 1) AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle Failed asserting that true is false. /home/action/workspace/lab/PsySH/tests/Patterns/Decorator/TitledWindowTest.php:46 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
У нас есть тест, файл и строка, где генерируется ошибка. Давайте посмотрим на TitledWindowTest.php
<?php namespace AcmeTest\Patterns\Decorator; use PHPUnit_Framework_TestCase; use Acme\Patterns\Decorator\TitledWindow; use ReflectionMethod; class TitledWindowTest extends PHPUnit_Framework_TestCase { public function testRender() { /* some test code here */ } public function testAddTitle() { $renderString = 'bar'; $window = $this->getMock('Acme\Patterns\Decorator\SimpleWindow'); $window->expects($this->any())->method('render')->will($this->returnValue($renderString)); $titledWindow = new TitledWindow($window); $reflectionMethod = new ReflectionMethod($titledWindow, 'addTitle'); $reflectionMethod->setAccessible(true); $rs = $reflectionMethod->invoke($titledWindow); $this->assertFalse(empty($rs)); } }
Если вы не знакомы с phpUnit, не обращайте слишком много внимания на код. TitledWindow::addTitle()
говоря, я настраиваю все, чтобы проверить метод TitledWindow::addTitle()
, и ожидаю получить непустое значение.
Итак, как мы используем PsySh, чтобы проверить, что происходит? Просто добавьте метод Shell::debug()
как мы делали ранее.
<?php namespace DigitecTest\Patterns\Decorator; use PHPUnit_Framework_TestCase; use Digitec\Patterns\Decorator\TitledWindow; use ReflectionMethod; class TitledWindowTest extends PHPUnit_Framework_TestCase { public function testRender() { /* Some test code here */ } public function testAddTitle() { $renderString = 'bar'; $window = $this->getMock('Digitec\Patterns\Decorator\SimpleWindow'); $window->expects($this->any())->method('render')->will($this->returnValue($renderString)); $titledWindow = new TitledWindow($window); $reflectionMethod = new ReflectionMethod($titledWindow, 'addTitle'); $reflectionMethod->setAccessible(true); $rs = $reflectionMethod->invoke($titledWindow); \Psy\Shell::debug(get_defined_vars()); $this->assertFalse(empty($rs)); } }
Мы готовы к рок!
$ phpunit --verbose --debug --filter=TitledWindowTest::testAddTitle PHPUnit 4.0.14 by Sebastian Bergmann. Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml Starting test 'AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle'. Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman >>>
Так что в $rs
у нас должна быть строка; посмотрим, что у нас на самом деле есть.
>>> $rs => null
Нулевое значение, неудивительно, что тест не удался… Давайте проверим исходный код TitledWindow::addTitle()
. Если мы выполним команду ls
, мы увидим, что у нас есть метод этого объекта, доступный через объект $titledWindow
.
>>> show $titledWindow->addTitle protected function addTitle() protected function addTitle() { $returnString = <<<EOL +-------------+ |Title | EOL; echo $returnString . PHP_EOL; } >>>
Есть ошибка. Метод повторяет значение, а не возвращает его. Несмотря на то, что приложение работает нормально, с помощью модульного тестирования и PsySH мы обнаружили дефект и теперь можем его исправить.
Вывод
Эта статья не должна была быть исчерпывающей, чтобы продемонстрировать весь потенциал PsySH. Есть и другие интересные функции (например, «док»), которые вы должны попробовать. Сам по себе PsySH может быть не очень полезен, но в сочетании с другими инструментами и вашими умными возможностями отладки он может оказаться ценным активом.