Extensions - Typed PHP: Stronger Types For Cleaner Code (2014)

Typed PHP: Stronger Types For Cleaner Code (2014)

Extensions

Vagrant + Phansible

Many of the libraries, we will be working with, require a bit of special installation. Instead of labouring away at a guide for each operating system, we’re going to look at using Vagrant for our development environment.

Vagrant (in case you haven’t heard of it yet) is a programatic interface for managing virtual machines. If you’ve ever set up a virtual machine, installed the operating system, installed the development tools, you will know how much time it takes to do well. This process can be automated with Vagrant.

Vagrant depends on underlying virtualisation providers and provisioners. We’ll look at using VirtualBox as the virtualisation provider and Ansible as the provisioner.

Installing

To get VirtualBox installed, go to https://www.virtualbox.org/wiki/Downloads and download the installer for your operating system.

Once you have downloaded and installed VirtualBox, you should be able to install Vagrant. Go to http://www.vagrantup.com/downloads.html and download the installer for your operating system.

We’ll also need to install Ansible so we can use play books to provision the virtual machine. Go to http://docs.ansible.com/intro_installation.html and download the installer for your operating system.

Provisioning

Provisioning is just another word for a set of instructions that tell Vagrant which dependencies to install for you. There are many kinds of Vagrant provisioners, but the only one we will use is called Ansible.

We’ll use http://phansible.com to do most of the heavy-lifting.

Set the following options:

1. Operating System: Ubuntu Precise Pangolin 64

2. Webserver: Nginx + PHP5-FPM

3. PHP: 5.5

4. Composer: Enabled

5. PHP Modules: php-pear php5-cli php5-common

When you click “Generate”, you’ll start downloading an archive of files. Extract these into a working directory and start up Terminal.

These files are instruction files (Ansible-specific YAML syntax) which describe which dependencies Vagrant must automatically install. You shouldn’t need to change them to get the PHP stack working, but feel free to familiarise yourself with what they are doing.

To start the virtual machine, run the following command:

1 $ vagrant up

2

3 Bringing machine 'default' up with 'virtualbox' provider...

4 ==> default: Importing base box 'precise64'...

5 ==> default: Matching MAC address for NAT networking...

6 ==> default: Setting the name of the VM: Default

7 ==> default: Clearing any previously set network interfaces...

You may be asked to provide an administrator password, as part of setting up the virtual machine. This will allow the NFS shared folders to be set up.

Once the virtual machine is initialised, you can log into it with:

1 $ vagrant ssh

2

3 Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

4

5 * Documentation: https://help.ubuntu.com/

6 Welcome to your Vagrant-built virtual machine.

7 Last login: Tue May 27 11:16:05 2014 from 10.0.2.2

8

9 vagrant@precise64:~$

You can also check the installed version of PHP (and that the CLI was installed correctly) with:

1 $ php -v

2

3 PHP 5.5.12-2+deb.sury.org~precise+1 (cli) (built: May 12 2014 13:46:35)

4 Copyright (c) 1997-2014 The PHP Group

5 Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies

6 with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2014, by Zend Technologies

Vagrant Commands

There are a few Vagrant commands you’re likely to use often:

1 $ vagrant up

This command will start the Vagrant virtual machine, and run any outstanding provisioning. That means the first time you run this command, it will take longer boot (depending on the complexity of your provisioning scripts).

1 $ vagrant halt

This command will shut the virtual machine down. It will try to gracefully shut the machine down.

1 $ vagrant destroy

This command will remove the virtual machine and clean up settings applied when it was created. If you break something inside the visual machine, and you want to reset it to the default state, you’ll want to run this command followed by vagrant up.

1 $ vagrant ssh

This command will take you inside the virtual machine, just as if you were connecting to a remote server. Inside the virtual machine, you can run any of the commands usually supported by the guest operating system, including executing PHP scripts against the packages installed on the virtual machine.

http://phansible.com is the brain-child of Erika Heidi. She is also the author of Vagrant Cookbook. I highly recommend reading this book, if you have any questions or simply want to know more about Vagrant!

SPL Types

SPL (or Standard PHP Library) is a library of additional types to augment those native to core PHP. There are some popular classes (like LogicException and ArrayObject). Some of the SPL ships with standard PHP installations. The parts we’re going to look at shortly do not…

You can find these mystery libraries at http://www.php.net/manual/en/book.spl-types.php

This section assumes you’re using the Vagrant box explained earlier. If not, please set that up first. These commands are Linux-specific and depend on the pre-installed modules explained earlier.

Installing SPL Types

To install them, run the following commands:

1 $ sudo apt-get install libpcre3-dev php5-dev

2

3 Reading package lists... Done

4 Building dependency tree

5 Reading state information... Done

6 The following extra packages will be installed:

7 autoconf automake autotools-dev build-essential...

1 $ sudo pecl install SPL_Types

2

3 downloading SPL_Types-0.4.0.tgz ...

4 Starting to download SPL_Types-0.4.0.tgz (8,388 bytes)

5 .....done: 8,388 bytes

6 6 source files, building

7 running: phpize...

These two commands install the pre-requisites for compiling PECL extensions. PECL is a repository just like PEAR. If you’ve heard of neither, then don’t worry. All you need to know is that the SPL Types are hosted here, so to install them we need to be able to compile PECL extensions.

1 $ sudo bash -c "echo extension=spl_types.so >> /etc/php5/cli/php.ini"

This command appends extension=spl_types.so to the php.ini file (as per the installation instructions).

1 $ sudo service php5-fpm restart

2

3 php5-fpm stop/waiting

4 php5-fpm start/running, process...

This command restarts PHP-FPM - the process that interprets PHP command line instructions and Nginx web requests. These commands should have installed the SPL Types, but just to be sure, run the following command:

1 $ php -i | grep SPL_Types

2 SPL_Types

If you see that SPL_Types line, then you should be good to go!

Using SPL Types

Let’s look at a few examples of how these classes can be used:

1 <?php

2

3 class NumberType extends SplFloat

4 {

5 /**

6 * @return float

7 */

8 public function toInteger()

9 {

10 return round($this);

11 }

12

13 /**

14 * @return string

15 */

16 public function toString()

17 {

18 return (string) $this;

19 }

20 }

21

22 $number = new NumberType(13.86);

23

24 print $number->toInteger(); // 14

25 print $number->toString(); // "13.86"

26

27 print (float) $number + 1.00; // 14.86

28 print $number * 12; // 156

The toInteger and toString methods do similar things to the box classes. The magic happens when we do basic arithmetic with the $number object. SPL Types are automatically unboxed when they are used in arithmetic expressions, or cast or concatenated. Any operator that would normally with with a scalar type will work with the corresponding SPL Type.

Here’s another example:

1 <?php

2

3 class StringType extends SplString

4 {

5 /**

6 * @param int $start

7 * @param mixed $length

8 *

9 * @return StringType

10 */

11 public function slice($start = 0, $length = null)

12 {

13 if ($length === null) {

14 return new static(substr($this, $start));

15 }

16

17 return new static(substr($this, $start, $length));

18 }

19 }

20

21 $string = new StringType("Hello World");

22

23 print $string->slice(6); // "World"

We can design our types so that they return new instances. This gives us a simple chaining interface.

Be careful when assuming the return type of native PHP functions. Be sure to check them before the call to new static() or you may encounter fatal errors.

Scalar Objects

Nikita Popov is a prolific contributor to PHP (both core and user-land). He’s made libraries such as PHP-Parser which is used all over the web, and in popular frameworks (like Laravel). He’s championed many multiple RFCs, which have become parts of core PHP.

He’s also created a custom extension which allows the registration of custom type handlers. You can find it at https://github.com/nikic/scalar_objects.

We’re going to install and use this module to get even closer to our ideal type handling situation…

This section assumes you’re using the Vagrant box explained earlier. If not, please set that up first. These commands are Linux-specific and depend on the pre-installed modules explained earlier.

Installing Scalar Objects

First up, we need to install the Git command-line tool:

1 $ sudo apt-get install git

2

3 Reading package lists... Done

4 Building dependency tree

5 Reading state information... Done

6 The following extra packages will be installed:

7 git-man liberror-perl...

Following this, we can clone and build the extension:

1 $ git clone https://github.com/nikic/scalar_objects.git

2

3 Cloning into 'scalar_objects'...

4 remote: Reusing existing pack: 213, done.

5 remote: Total 213 (delta 0), reused 0 (delta 0)

6 Receiving objects: 100% (213/213), 75.36 KiB, done.

7 Resolving deltas: 100% (112/112), done.

1 $ cd scalar_objects && phpize && ./configure && make && sudo make install

These commands will the Scalar Objects extension, but we still need to add it to the configuration: $ sudo bash -c "echo extension=scalar_objects.so >> /etc/php5/cli/php.ini"

This command resembles the one we used to install the SPL Types. We’re essentially doing the same thing, so we need to restart PHP5-FPM:

1 $ sudo service php5-fpm restart

2

3 php5-fpm stop/waiting

4 php5-fpm start/running, process...

This should complete the process of installing the Scalar Objects extension, but we can make sure it’s working by running the following command:

1 $ php -i | grep scalar

2

3 scalar_objects

4 scalar-objects support => enabled

Using Scalar Objects

The new extension adds a method we can use to register these type handlers. This is how you we can use it:

1 <?php

2

3 class StringHandler

4 {

5 /**

6 * @param int $start

7 * @param mixed $length

8 *

9 * @return StringType

10 */

11 public function slice($start = 0, $length = null)

12 {

13 if ($length === null) {

14 return substr($this, $start);

15 }

16

17 return substr($this, $start, $length);

18 }

19 }

20

21 register_primitive_type_handler("string", "StringHandler");

22

23 $string = "Hello World";

24

25 print $string->slice(6); // "World"

This is easier than boxing scalar types, as we don’t have to pull native types out of class instances. This is easier than SPL Types, as we don’t have to put native types into class instances.

There are seven supported types:

· null

· bool

· int

· float

· string

· array

· resource

You may be wondering whether this extension plays nicely with SPL Types. The answer is probably not. You shouldn’t mix these extensions and since the Scalar Objects extension does everything SPL Types you shouldn’t need both.

Zephir

Zephir is a framework for writing compilable and installable PHP extensions, using a PHP superset language sharing some similarity with C code. Zephir isn’t strictly a PHP extension, nor are Zephir libraries written in true PHP.

It’s part of the same collective from which the Phalcon framework comes, and Phalcon is itself a PHP extension. This should get interesting!

Installing Zephir

Zephir requires a few libraries, in order to compile correctly. We can install these with:

1 $ sudo apt-get install git gcc make re2c php5 php5-json php5-dev libpcre3-dev

Next, we need to install the JSON-C library (which Zephir uses to compile extensions):

1 $ git clone https://github.com/json-c/json-c.git && cd json-c && sh autogen.sh\

2 && ./configure && make && sudo make install

These commands will clone the JSON-C repository, configure and compile it. Finally, we need to install Zephir:

1 $ git clone https://github.com/phalcon/zephir && cd zephir && ./install -c

That should have installed a usable version of Zephir. You can check that it’s working by heading into the clone folder and running:

1 $ zephir version

Using Zephir

Using Zephir is relatively simple (considering the work that actually goes on). Let’s begin by initialising a new extension skeleton project:

1 $ zephir init type

This will create a skeleton project folder in the current working directory. Navigate into the new type directory and run:

1 $ ls -la

2

3 ext/ type/ config.json

Extension classes go in the type folder (it’s specific to the name of the extension, which we gave the init command). Make a find in there, called StringType.zep, and open that file in your editor.

The Zephir syntax is quite similar to PHP, but with a twist of C style. You can find a reasonable amount of documentation at http://www.zephir-lang.com/index.html.

Create the following class:

1 namespace Type;

2

3 class StringType

4 {

5 protected data;

6

7 public function __construct(var data)

8 {

9 let this->data = data;

10 }

11

12 public function length()

13 {

14 return strlen(this->data);

15 }

16 }

Other than the missing $ symbols, and the var/let keywords added, this is pretty understandable. Save the file and (from the base extension folder) run:

1 $ zephir build

2

3 Compiling...

4 /bin/bash /vagrant/zephir/type/ext/libtool --mode=compile gcc -I. -I/vagrant/\

5 zephir/type/ext -DPHP_ATOM_INC -I/vagrant/zephir/type/ext/include -I/vagrant/z\

6 ephir/type/ext/main -I/vagrant/zephir/type/ext -I/usr/include/php5 -I/usr/incl\

7 ude/php5/main -I/usr/include/php5/TSRM -I/usr/include/php5/Zend -I/usr/include\

8 /php5/ext -I/usr/include/php5/ext/date/lib -DHAVE_CONFIG_H -O2 -fvisibility=\

9 hidden -Wparentheses -flto -c /vagrant/zephir/type/ext/type/stringtype.zep.c\

10 -o type/stringtype.lo...

Zephir cross-compiles the extension class files to Plain Ol’ C, and adds the class loading code. The end of the build output should look something like:

1 Installing...

2 Extension installed!

3 Add extension=type.so to your php.ini

4 Don't forget to restart your web server

…So we need to add the extension to the php.ini file:

1 $ sudo bash -c "echo extension=type.so >> /etc/php5/cli/php.ini"

This will have installed the Type extension we just created. We can check that it’s installed by running:

1 $ php -i | grep "type => enabled"

2

3 type => enabled

If you see that line returned, you know the extension is installed, and ready to go.

Using this new extension is as simple as:

1 <?php

2

3 $string = new Type\StringType("Hello World");

4

5 print $string->length();

The namespace and class reside completely within the compiled extension file. Zephir extensions can use pre-existing core and extension namespaces/classes. The can be used by Plain Ol’ PHP code (provided the extension is registered by the time it’s used).

Zephir extensions can even override core functions, with better-performing versions.

Conclusion

Extensions make our lives significantly easier, by handling things like boxing and unboxing for us. They let us create better-performing code (as in the case of Zephir) and stricter types (as in the case of SPL Types).

We don’t have to use these to make a cleaner system. If we do, we can expect to have a much strong type system, without the hard work that library-only code expects of us.