Blog | Studio Emma - Magento & Pimcore developmentMagento2 php7 development box
28 januari 2016

Magento2 php7 development box

Why?

We could argue its still early to start using PHP7 for our next generation webshops. But we must assure that what we build today will work tomorrow. In production environments it might still be early to use PHP7 due to the fact some 3rd party extensions are not yet ready for PHP7. But we can develop just fine on a PHP7 box.

Our box requirements

We will use the following components in our development machine:

  • ubuntu 14.04
  • nginx
  • mysql
  • php
  • ant
  • (optional) memcached
  • (optional) redis

To build a machine quickly we will use vagrant.

Vagrant setup

We are not using any special tool to build our vagrant setup. Just plain and simple vagrant init and add some tricks we learned over time.

# -*- mode: ruby -*-
# vi: set ft=ruby :
 
require 'yaml'
 
begin
      boxconfig = YAML.load_file('config.yml')
rescue Errno::ENOENT
      abort "No config.yml found. Copy config.yml.example to get started."
end
 
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what

# you're doing.
Vagrant.configure(2) do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
 
  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://atlas.hashicorp.com/search.
  config.vm.box = "ubuntu/trusty64"
 
  # Set the hostname so you can destinguish between your different projects
  config.vm.hostname = boxconfig['project']
 
  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false
 
  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network "private_network", ip: boxconfig['ip']
  if boxconfig['pubip']
    config.vm.network "public_network", ip: boxconfig['pubip']
  end
 
  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"
 
  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  if (RUBY_PLATFORM =~ /darwin/ || RUBY_PLATFORM =~ /linux/)
    config.vm.synced_folder boxconfig['path'], "/var/www/website", id: "website", type: "nfs", mount_options: ["rw","vers=3","udp","actimeo=2"]
  else
    config.vm.synced_folder boxconfig['path'], "/var/www/website", id: "website"
  end
 
  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  config.vm.provider "virtualbox" do |vb|
    vb.memory = boxconfig['memory']
    vb.cpus = boxconfig['cpus']
    if RUBY_PLATFORM =~ /linux/
      vb.customize ["modifyvm", :id, "--paravirtprovider", "kvm"]
    end
  end
 
  # stdin: is not a tty
  config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'"
  config.ssh.forward_agent = true
 
  config.vm.provision :shell, :path => 'magento2.sh'
end

As you already noticed we use a yaml configuration file to customize the box.

project: 'magento2'
ip: '192.168.254.10'
#pubip: '192.168.0.10'
path: '../magento2'
memory: 1024
cpus: 1

Install nginx

We will install nginx and let it run as the vagrant user.

#!/bin/bash
 
apt-get install -y nginx 
 
# create log folder in case it does not exist
mkdir -p /var/www/website/var/log
 
# install config
rm -f /etc/nginx/sites-enabled/*
install -Dm644 files/website.conf /etc/nginx/sites-enabled/
 
# run nginx as vagrant user
sed -e 's/^user.*/user vagrant;/' -i /etc/nginx/nginx.conf
 
service nginx restart

For the purpose of this box we will add a catchall server configuration.

upstream fastcgi_backend {
    server  127.0.0.1:9000;
}
 
server {
    listen 80 default_server;
 
    root /var/www/website/pub;
 
    index index.php;
    autoindex off;
    charset off;
 
    access_log /var/www/website/var/log/nginx_access.log;
    error_log /var/www/website/var/log/nginx_error.log debug;
 
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
 
    location /pub {
        location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) {
            deny all;
        }
        alias /var/www/website/pub;
    }
 
    location /static/ {
        location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ {
            add_header Cache-Control "public";
            expires +1m;
 
            if (!-f $request_filename) {
                rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
            }
        }
        location ~* \.(zip|gz|gzip|bz2|csv|xml)$ {
            add_header Cache-Control "no-store";
           expires    off;
 
            if (!-f $request_filename) {
                rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
            }
        }
        if (!-f $request_filename) {
            rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
        }
    }
 
    location /media/ {
        try_files $uri $uri/ /get.php?$args;
 
        location ~ ^/media/theme_customization/.*\.xml {
            deny all;
        }
 
        location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ {
           add_header Cache-Control "public";
            expires +1m;
            try_files $uri $uri/ /get.php?$args;
       }
        location ~* \.(zip|gz|gzip|bz2|csv|xml)$ {
            add_header Cache-Control "no-store";
            expires    off;
            try_files $uri $uri/ /get.php?$args;
        }
    }
 
 
    location /media/customer/ {
        deny all;
    }
 
    location /media/downloadable/ {
        deny all;
    }
 
    location /media/import/ {
        deny all;
    }
 
    location ~ cron\.php {
        deny all;
    }
 
    location ~ (index|get|static|report|404|503)\.php$ {
        try_files $uri =404;
        fastcgi_pass   fastcgi_backend;
 
        fastcgi_read_timeout 600s;
        fastcgi_connect_timeout 600s;
 
        fastcgi_intercept_errors on;
 
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param  SERVER_NAME  'vagrant';
        include        fastcgi_params;
    }
} 

Install mysql

We must use mysql 5.6 or above since Magento 2 uses features that were introduced with mysql 5.6

MariaDB 10 and above also have the required features. There has been an issue where we could not install Magento 2 with MariaDB when we were using the web setup.

#!/bin/bash
 
echo 'mysql-server-5.6  mysql-server/root_password password toor' \
    | debconf-set-selections
echo 'mysql-server-5.6  mysql-server/root_password_again password toor' \
    | debconf-set-selections
 
apt-get install -y mysql-server-5.6 mysql-client-5.6
 
echo "[mysqld]" > /etc/mysql/conf.d/se.cnf
echo "sql_mode=NO_ENGINE_SUBSTITUTION" >> /etc/mysql/conf.d/se.cnf
echo "performance_schema = 0" >> /etc/mysql/conf.d/se.cnf
echo "bind-address = 0.0.0.0" >> /etc/mysql/conf.d/se.cnf
 
service mysql stop
sleep 2
service mysql start
 
mysql -uroot -ptoor -e \
    "grant all on *.* to 'root'@'%' identified by 'toor'; flush privileges;"

Install php

Thanks to the composer.json file we already know what php extensions are needed to run magento. To run PHP7 we will use the ppa of ondrej. We will ofcourse add xdebug so we can run some debugging on our development box. Since there is not yet an xdebug package available for PHP7 we will compile our own from git HEAD.

We must increase xdebug.max_nesting_level because Magento is a fairly complex system it easlily exceeds the default set in xdebug (see). We increased it to 400 just to be sure.

#!/bin/bash
 
add-apt-repository -y ppa:ondrej/php-7.0
apt-get update
 
# php fpm
apt-get install -y \
    php-cli php-fpm php-curl php-gd php-intl php-json php-mysql php-pear \
    php-dev php-mcrypt
 
## build XDEBUG
git clone https://github.com/xdebug/xdebug.git
cd xdebug
phpize
./configure --prefix=/usr --enable-xdebug
make
cd debugclient
./buildconf
./configure --prefix=/usr
make
make install
cd ..
make install
cd ..
rm -rf xdebug
 
cat >> /etc/php/mods-available/xdebug.ini <<-EOF
zend_extension=xdebug.so
xdebug.remote_enable = 1
xdebug.remote_connect_back = 1
xdebug.max_nesting_level=400
EOF

install -Dm644 files/custom.ini /etc/php/mods-available/custom.ini

(
    cd /etc/php/7.0/cli/conf.d/
    ln -s /etc/php/mods-available/xdebug.ini 20-xdebug.ini
    ln -s /etc/php/mods-available/custom.ini 20-custom.ini
    cd /etc/php/7.0/fpm/conf.d/
    ln -s /etc/php/mods-available/xdebug.ini 20-xdebug.ini
    ln -s /etc/php/mods-available/custom.ini 20-custom.ini
)
 
# php-fpm as vagrant user and listen on tcp
sed -e 's/^user = .*/user = vagrant/' \
    -e 's/^group = .*/group = vagrant/' \
    -e 's/^listen.owner = .*/listen.owner = vagrant/' \
    -e 's/^listen.group = .*/listen.group = vagrant/' \
    -e 's/^listen = .*/listen = 127.0.0.1:9000/' \
    -i /etc/php/7.0/fpm/pool.d/www.conf
 
service php7.0-fpm restart
 
# install composer
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
chmod +x /usr/local/bin/composer

We add our timezone to a custom ini. There we also set the max execution time recommended for Magento 2.

date.timezone = "Europe/Brussels"
display_errors = On
error_reporting = -1
max_execution_time = 600

Install memcached

Since there is not yet a php-memcached package we must compile our own extension for php.

#!/bin/bash

apt-get install -y memcached pkg-config libmemcached-dev

git clone https://github.com/php-memcached-dev/php-memcached.git
cd php-memcached
# we need the php7 branch
git checkout php7
phpize
./configure --prefix=/usr
make
make install
cd ..
rm -rf php-memcached
echo "extension=memcached.so" > /etc/php/mods-available/memcached.ini
(
    cd /etc/php/7.0/cli/conf.d/
    ln -s /etc/php/mods-available/memcached.ini 20-memcached.ini
    cd /etc/php/7.0/fpm/conf.d/
    ln -s /etc/php/mods-available/memcached.ini 20-memcached.ini
)
 
service php7.0-fpm restart
service memcached restart

Install redis

To use Redis we also need to buid our experimental php redis extesion.

#!/bin/bash

add-apt-repository -y ppa:chris-lea/redis-server
apt-get update
apt-get install -y redis-server

git clone https://github.com/phpredis/phpredis.git
cd phpredis
# we need the php7 branch
git checkout php7
phpize
./configure --prefix=/usr
make
make install
cd ..
rm -rf phpredis
echo "extension=redis.so" > /etc/php/mods-available/redis.ini
(
    cd /etc/php/7.0/cli/conf.d/
    ln -s /etc/php/mods-available/redis.ini 20-redis.ini
    cd /etc/php/7.0/fpm/conf.d/
    ln -s /etc/php/mods-available/redis.ini 20-redis.ini
)
 
service php7.0-fpm restart

Ant

We use ant for our build/setup tasks. We also add tasks that make development work easier.

<?xml version="1.0" encoding="UTF-8"?>
<project name="magento2" default="phpunit">
    <!-- ****************************** -->
    <!-- DEV TASKS                      -->
    <!-- ****************************** -->
 
    <!-- install project dependencies with composer -->
    <target name="composer" description="install composer dependencies">
        <exec executable="composer" failonerror="true">
            <arg value="install" />
        </exec>
    </target>
 
    <!-- install local grunt part -->
    <target name="local-install-grunt" description="install project local part of grunt">
        <exec executable="npm">
            <arg value="install" />
            <arg value="grunt" />
            <arg value="--save-dev" />
        </exec>
        <exec executable="npm">
            <arg value="install" />
        </exec>
    </target>
 
    <!-- create database for this project -->
    <target name="create-database" description="create new database">
        <exec executable="mysql">
            <arg value="-uroot" />
            <arg value="-ptoor" />
            <arg value="-e" />
            <arg value="CREATE DATABASE IF NOT EXISTS `magento2_demo` DEFAULT CHARSET utf8;" />
        </exec>
    </target>
 
    <!-- fresh magento install -->
    <target name="install" description="install magento2 on this box"
        depends="create-database">
        <exec executable="${basedir}/bin/magento">
            <arg value="setup:install" />
            <arg value="--db-host=127.0.0.1" />
            <arg value="--db-name=magento2_demo" />
            <arg value="--db-user=root" />
            <arg value="--db-password=toor" />
            <arg value="--backend-frontname=admin" />
            <arg value="--base-url=http://magento2.dev" />
            <arg value="--language=nl_NL" /> <!-- no nl_BE available -->
            <arg value="--currency=EUR" />
            <arg value="--timezone=Europe/Brussels" />
            <arg value="--admin-lastname=emma" />
            <arg value="--admin-firstname=studio" />
            <arg value="--admin-email=test@studioemma.eu" />
            <arg value="--admin-user=admin" />
            <arg value="--admin-password=toor123" />
            <arg value="--use-secure=0" />
            <arg value="--use-rewrites=1" />
            <arg value="--use-secure-admin=0" />
            <arg value="--session-save=files" />
        </exec>
    </target>
 
    <!-- ****************************** -->
    <!-- BUILD TASKS                    -->
    <!-- ****************************** -->
    <!-- run unittests -->
    <target name="phpunit" description="PHPUnit testing" depends="composer">
        <exec executable="${basedir}/vendor/bin/phpunit" failonerror="true">
            <arg value="--configuration=dev/tests/unit/phpunit.xml.dist"/>
            <arg value="--log-junit=${basedir}/test-reports/junit.xml"/>
        </exec>
    </target>
</project>

Install and run Magento 2

To make sure our box is working fine we'll do a quick install and see if everything works.

$ ant install
Unable to locate tools.jar. Expected to find it in /usr/lib/jvm/java-7-openjdk-amd64/lib/tools.jar
Buildfile: /var/www/website/build.xml

create-database:
     [exec] Warning: Using a password on the command line interface can be insecure.

install:
     [exec] Starting Magento installation:
     [exec] File permissions check...
     [exec] [Progress: 1 / 311]
     [exec] Enabling Maintenance Mode...
     [exec] [Progress: 2 / 311]
     [exec] Installing deployment configuration...
     [exec] [Progress: 3 / 311]
     [exec] Installing database schema:
     [exec] Schema creation/updates:
     [exec] Module 'Magento_Store':
     [exec] Installing schema..
...
     [exec] [Progress: 308 / 311]
     [exec] Caches clearing:
     [exec] Cache cleared successfully
     [exec] [Progress: 309 / 311]
     [exec] Disabling Maintenance Mode:
     [exec] [Progress: 310 / 311]
     [exec] Post installation file permissions check...
     [exec] For security, remove write permissions from these directories: '/var/www/website/app/etc'
     [exec] [Progress: 311 / 311]
     [exec] [SUCCESS]: Magento installation complete.
     [exec] [SUCCESS]: Magento Admin URI: /admin

BUILD SUCCESSFUL
Total time: 21 seconds

Lets see:

initial homepage

homepage without browsercache

homepage with browsercache

As we can see the initial homepage load is incredibly slow. But we did not have any of the static files already available so the request genereates all the required css which takes a serious amount of time. The subsequent requests are much faster. We did try one with empty browser cache which gives us a 2s load and when we profit from the browsercache we get approximatly 700ms homepage load.

Conclusion

It looks like default PHP7 is ready. But from the moment you start depending on some external extensions its still a bit early. The extensions mentioned above are open source so those might be available in a reasonably short timeframe in stable versions. Some extensions are only available as binaries, if you depend on one of those you could still have a long waiting time ahead of you before you can switch to PHP7.


Geschreven door:

Ike Devolder

Terug naar blog overzicht

Blijf op de hoogte van onze activiteiten