[Из песочницы] Настройка окружения для PHP разработчиков

В данной статье мы рассмотрим настройку окружения для PHP разработчиков с использованием Vagrant, Docker, Xdebug, PHPUnit, и интеграцию с IDE PHPStorm.

10204de2e84644048814d300cddafdbf.png + 7c2d206c893e420eb002553c21370331.png

Требования


  • Окружение должно развертываться на Windows, MacOS, Linux
  • Установка должна быть максимально простой
  • Быстрая работа с файловой системой
  • Интеграция с IDE PHPStorm
  • Несколько версии PHP интерпретаторов (5.6, 7.0)


Подготовка


Мы не будем заострять свое внимание на установке необходимых инструментов под определенные ОС, инструкции по установке есть на официальных сайтах, ссылки прилагаются.
  • Устанавливаем VirtualBox
  • Устанавливаем Docker Toolbox
  • Устанавливаем Vagrant

Что входит в сборку?


  • Nginx
  • MySQL 5 (Percona)
  • MongoDB 3
  • Redis 3
  • PHP 5.6, 7.0 CLI + FPM

PHP-extensions
5.6
  • cli
  • fpm
  • bcmath
  • gd
  • gmp
  • intl
  • mbstring
  • mcrypt
  • pdo
  • mysqlnd
  • crypto
  • geoip
  • imagick
  • jsonc
  • memcache
  • memcached
  • mongodb
  • ssh2
  • xdebug
  • soap
  • xml
  • opcache
  • redis
7.0
  • cli
  • fpm
  • bcmath
  • gd
  • gmp
  • intl
  • mbstring
  • mcrypt
  • pdo
  • mysqlnd
  • crypto
  • geoip
  • imagick
  • memcache
  • memcached
  • mongodb
  • ssh2
  • xdebug
  • soap
  • xml
  • opcache
  • redis

Настройка и конфигурирование


Внимание! Все описанные настройки и файлы выложены на GitHub. Если у вас возникают вопросы по использованию Vagrant или Docker обратитесь к официальной документации.

Развертывание окружения чуть-чуть отличается на разных ОС:

  • MacOS все пройдет, как положено без дополнительных действий.
  • Windows необходимо запустить вспомогательный скрипт hosts-set.cmd для установки прав текущему пользователю на запись для hosts файла до запуска vagrant up, также если у вас имя текущего пользователя содержит любые символы non-ACSII, то вам придется сменить путь до вашей рабочей директории vagrant.d используя переменную окружения VAGRANT_HOME.
  • Linux на усмотрение, можно поставить Vagrant с NFS сервером или просто использовать docker-compose инструкции.

hosts-set.cmd
@if (1==1) @if(1==0) @ELSE
@echo off&SETLOCAL ENABLEEXTENSIONS
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"||(
    cscript //E:JScript //nologo "%~f0"
    @goto :EOF
)
CACLS %SystemRoot%\system32\drivers\etc\hosts /E /G %USERNAME%:W
@goto :EOF
@end @ELSE
ShA=new ActiveXObject("Shell.Application")
ShA.ShellExecute("cmd.exe","/c \""+WScript.ScriptFullName+"\"","","runas",5);
WScript.Sleep(500)
@end

Гостевая ОС будет работать на Ubuntu 14.04 (Trusty), статическом IP адресе 10.0.0.2 и в качестве share-папки будем использовать монтирование через NFS.

Для удобства создайте папку под названием dockrant (Docker + Vagrant = Dockrant), далее в статье мы будем отталкиваться от этой папки.

Определимся со структурой папок:

./dockrant — наша корневая папка
./dockrant/ssh — SSH ключики для гостевой ОС (в данном туториале используем стандартные ключи vagrant)
./dockrant/share — share-папка между хост и гостевой ОС
./dockrant/share/tools — папка с нашими инструментами и bash скриптами
./dockrant/vagrant/build —  исполняемые bash скрипты при поднятии гостевой ОС

Теперь нам необходимо по структуре папок описанных выше положить все необходимые наши скрипты и инструкции, пойдем по списку.

./dockrant/ssh — положим два файла id_rsa (приватный ключ) и id_rsa.pub (публичный ключ)

id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI
w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP
kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2
hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO
Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW
yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd
ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1
Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf
TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK
iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A
sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf
4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP
cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk
EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN
CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX
3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG
YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj
3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+
dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz
6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC
P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF
llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ
kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH
+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ
NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=
-----END RSA PRIVATE KEY-----
id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key

./dockrant/share/ — создадим файл phpinfo.php с phpinfo () функцией, чтобы потом проверить работоспособность
phpinfo.php

./dockrant/share/tools/ — положим несколько вспомогательных скриптов для запуска PHP интерпретаторов из под Docker exec и PHPUnit (скачайте c сайта phpunit.phar)
php56
#!/bin/bash
CMD="export IDE_PHPUNIT_PHPUNIT_PHAR='$IDE_PHPUNIT_PHPUNIT_PHAR'"
CMD+=" && export IDE_PHPUNIT_VERSION='$IDE_PHPUNIT_VERSION'"
CMD+=" && export SSH_CLIENT='$SSH_CLIENT'"
CMD+=" && export JETBRAINS_REMOTE_RUN='$JETBRAINS_REMOTE_RUN'"
CMD+=" && export DEBUG_CONFIG='$XDEBUG_CONFIG'"
CMD+=" && export XDEBUG_CONFIG='$XDEBUG_CONFIG'"
CMD+=" && php56 $@"
docker exec share_php_1 bash -c "$CMD"
php56tty
#!/bin/bash
CMD+="php56 $@"
docker exec -t -i share_php_1 bash -c "$CMD"
php70
#!/bin/bash
CMD="export IDE_PHPUNIT_PHPUNIT_PHAR='$IDE_PHPUNIT_PHPUNIT_PHAR'"
CMD+=" && export IDE_PHPUNIT_VERSION='$IDE_PHPUNIT_VERSION'"
CMD+=" && export SSH_CLIENT='$SSH_CLIENT'"
CMD+=" && export JETBRAINS_REMOTE_RUN='$JETBRAINS_REMOTE_RUN'"
CMD+=" && export DEBUG_CONFIG='$XDEBUG_CONFIG'"
CMD+=" && export XDEBUG_CONFIG='$XDEBUG_CONFIG'"
CMD+=" && php70 $@"
docker exec share_php_1 bash -c "$CMD"
php70tty
#!/bin/bash
CMD+="php70 $@"
docker exec -t -i share_php_1 bash -c "$CMD"

Для php* скриптов мы не используем TTY, эти скрипты нужны для корректной настройки интеграции с IDE в использовании remote интерпретаторов, скрипты php*tty нужны для ручного использования запуска PHP скриптов, в том числе и в интерактивном режиме php -a.

./dockrant/vagrant/build/php/ — положим init.sh bash скрипт для копирования исполняемых файлов запуска PHP интерпретаторов.

init.sh
#!/bin/bash
cp /share/tools/php56 /usr/bin/php56 && chmod +x /usr/bin/php56
cp /share/tools/php70 /usr/bin/php70 && chmod +x /usr/bin/php70
cp /share/tools/php56tty /usr/bin/php56tty && chmod +x /usr/bin/php56tty
cp /share/tools/php70tty /usr/bin/php70tty && chmod +x /usr/bin/php70tty

./dockrant/vagrant/build/docker/ — положим init.sh и docker-compose.sh bash скрипты для установки docker-compose и команд на сборку контейнеров при поднятии гостевой ОС.
init.sh
#!/bin/bash
/usr/local/bin/docker-compose -f /share/docker-compose.yml build
/usr/local/bin/docker-compose -f /share/docker-compose.yml up -d
docker-compose.sh
#!/bin/bash
curl -L https://github.com/docker/compose/releases/download/1.8.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

Docker-compose


Создадим файл ./dockrant/share/docker-compose.yml

Теперь опишем инструкции для создания docker-контейнеров, мы не будем описывать инструкции для сборки каждого образа, возьмём готовые.

Собираем из следующих образов:

  • redis:3
  • percona:5
  • gurukami/php:5.6
  • gurukami/php:7.0 (для FPM используется по умолчанию)
  • gurukami/nginx
  • mongo:3

docker-compose.yml
version: '2'
services:
  redis:
    image: redis:3.0
  mysql:
    image: percona:5
    environment:
      - MYSQL_ROOT_PASSWORD=gurukami
    volumes:
      - /share:/share
  php56:
    image: gurukami/php:5.6
    links:
      - mysql
      - mongo
      - redis
    volumes:
      - /share:/share
      - /home:/home
      - /tmp/:/tmp/
  php:
    image: gurukami/php:7.0
    links:
      - mysql
      - mongo
      - redis
    volumes:
      - /share:/share
      - /home:/home
      - /tmp/:/tmp/
  mongo:
    image: mongo:3
    ports:
      - "27017:27017"
  nginx:
    image: gurukami/nginx:latest
    depends_on:
      - php
    links:
      - php
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /share:/share

Vagrantfile


Создайте в папке dockrant файл Vagrantfile и запишите туда следующие инструкции
Vagrantfile
Vagrant.require_version ">= 1.8.6"

required_plugins = %w(vagrant-hostsupdater)
plugins_to_install = required_plugins.select { |plugin| not Vagrant.has_plugin? plugin }
if not plugins_to_install.empty?
  puts "Installing plugins: #{plugins_to_install.join(' ')}"
  if system "vagrant plugin install #{plugins_to_install.join(' ')}"
    exec "vagrant #{ARGV.join(' ')}"
  else
    abort "Installation of one or more plugins has failed. Aborting."
  end
end

Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"
  config.vm.hostname = "gurukami.local"

  config.ssh.insert_key = false

  config.hostsupdater.aliases = [
    "sandbox.local"
  ]

  config.nfs.map_uid = Process.uid
  config.nfs.map_gid = Process.gid

  config.vm.network :private_network, ip: "10.0.0.2"
  config.vm.network :forwarded_port, guest: 22, host: 2202, id: "ssh"

  config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
    vb.name = "Gurukami (Dockrant)"
    vb.memory = 1024
    vb.cpus = 2
  end

  config.vm.provision "fix-no-tty", type: "shell" do |s|
    s.privileged = false
    s.inline = "sudo sed -i '/tty/!s/mesg n/tty -s \\&\\& mesg n/' /root/.profile"
  end

  config.vm.provision "shell", inline: 'mkdir /vagrant', run: "once"
  config.vm.provision "shell", inline: 'echo "nameserver 8.8.8.8" >> /etc/resolv.conf', run: "always"
  config.vm.provision "shell", path: './vagrant/build/php/init.sh', run: "once"
  config.vm.provision "shell", path: './vagrant/build/docker/docker-compose.sh', run: "once"

  config.vm.provision :docker
  config.vm.provision "shell", path: './vagrant/build/docker/init.sh', run: "always"

  config.vm.synced_folder ".", "/vagrant", disabled: true
  config.vm.synced_folder "./share", "/share", nfs: true, nfs_udp: false
end


Тюнинг:
  • При необходимости шаринга можно добавить опцию config.vm.network: public_network, чтобы поднять на публичном интерфейсе или использовать vagrant share
  • Увеличить использование выделенной памяти vb.memory, по умолчанию стоит 1024 MB
  • Увеличить кол-во использованных процессоров vb.cpus, по умолчанию стоит 2

Запуск


В папке dockrant запускаем команду
vagrant up --provider virtualbox

После сборки откроем в браузере — sandbox.local/phpinfo.php
b76bd377e2f34e5c9e8996a24ac65ff5.png

Docker-machine


Для того чтобы наша виртуальная машина работала как docker-machine нам необходимо добавить её используя generic драйвер, запускаем из под папки dockrant
docker-machine create --driver generic --generic-ip-address=10.0.0.2 --generic-ssh-key ./ssh/id_rsa --generic-ssh-user vagrant sandbox

После создания docker-machine, гостевую машину нужно перезагрузить, т.к. после установки все docker контейнеры останавливаются
vagrant reload

PHP-CLI


Запуск PHP скриптов осуществляется на гостевой машине (10.0.0.2), помните, что между хост-машиной и гостевой есть /share папка.
php56 /path/to/file
php70 /path/to/file

Запуск PHP интерпретаторов в интерактивном режиме
php56tty -a
php70tty -a

Интеграция с PHPStorm


Мы настроим дебаггер с помощью XDebug, прикрутим PHPUnit, а также добавим управление нашими контейнерами через плагин Docker Integration, с помощью которого можно будет смотреть логи наших сервисов, перезагружать контейнеры, исполнять скрипты.

Добавим в настройки наши PHP интерпретаторы, открываем настройки Languages & Frameworks → PHP

dc6e2ac0ac1b46a099e16f16d6e3dada.png

Добавляем сначала 5.6 версию, потом повторяем для версии 7.0, нажимаем на кнопку

Затем нажав на + выбираем Remote…

a0557dfe02fe4f0c907761a1c7d0cd04.png

В всплывающем окне выбираем тип Vagrant и указываем путь до PHP интерпретатора на гостевой ОС /usr/bin/php56

4213756491b34cf58d51f35fe5dfec37.png

75b61bb0b27141ebbff8ee5804ae271d.png

Теперь добавим наш удаленный сервер в список для работы с дебаггером по запросу из браузера
Language & Frameworks → PHP → Servers. Пропишем path mappings до нашей /share папки чтобы IDE смог распознать запросы по путям между хост машиной и гостевой

8d81248a92d5400d8e7d897cb25a4e6a.png

Настроим PHPUnit, идем в настройки Language & Frameworks → PHP → PHPUnit и для каждой версии PHP интерпретатора пропишем путь до /share/tools/phpunit.phar

95cf6fa4c75949008d7541975558824f.png

4ce0a08a305d46f7864127143a55ded4.png

Мы почти готовы к отладке нашего приложения, но еще осталось настроить конфигурации запуска

5bbf06ac4166437494717c9386c0e442.png

Настроим удаленную отладку PHP Remote Debug
a9893472d66b477791b9fc660d5bd3fb.png

95716bf5fb4e41dea66309889432b626.png

Ставим breakpoint к примеру в файле phpinfo.php на 2 строчке, запускаем дебаггер, переходим в браузере на страницу sandbox.local/phpinfo.php и видим, что наш дебаггер сработал в IDE

d431145867db4ad69141bc8b8d4e8816.png

1831db0b1ec844abbdbd6b37727b4309.png

70982f417a6348e98e857bc9ee7344d9.png

Теперь перейдем к PHPUnit

91f849e8adb14b48b75111352b33956d.png

Создадим проcтенький Unit test файл под именем Test.php в ./dockrant/share папке
Test.php
assertTrue(true);
    }
}

Настроим конфигурацию запуска

6224b1ad3ff44368b4bf5465294e52c5.png

Запускаем Unit test

d97aca41534a4eef9646a9b13a4d4d62.png

d34b57aefe6949c1be5e09a5670a230f.png

Также можно запустить Unit test с дебаггером

01a48a89442e479cb964afc150ce9b28.png

1da7461bb37b4a71a6e9ddd5a2361862.png

Теперь настроим плагин Docker Integration, после установки плагина необходимо добавить нашу машину в настройках Build, Execution, Deployment → Docker как правило папки docker-machines лежат в вашей домашней директории текущего пользователя, нам нужен sandbox docker-machine который мы создавали по инструкции вверху используя generic драйвер

735d520221994df089eef84fcd92c624.png

Внизу вкладка Docker будет показана после перезагрузки IDE

16b5d4f89fb34b2a9c1d6abd6e139cf8.png

Итог


Мы имеем окружение с полным набором необходимых сервисов и утилит разворачиваемое на большинстве операционных систем.

Развертывание окружения было протестировано на:

  • Windows 10
  • MacOS Sierra 10.12
  • Ubuntu 14.04 Desktop

Спасибо за прочтение, надеюсь эта статья поможет вам организовать удобное окружение.

Комментарии (2)

  • 31 октября 2016 в 16:26 (комментарий был изменён)

    0

    Пожалуй, лучшее возможное, достойное завершение недавнего «цикла» статей про то, «как поднять две версии на одной машине».
    Спасибо!

    Единственное, что хотелось бы увидеть еще — сравнение производительности по сравнению с «локальной» установкой.

    • 31 октября 2016 в 16:31

      0

      если у вас хост linux и все в докере разницы по производительности практически нет. Если же хост mac/windows то добавляются накладные расходы на волумы с хоста, обычно либо шаред фолдеры (очень медленно) либо NFS (терпимо).

© Habrahabr.ru