Developing a PHP extension in CLion
Tags: php, pecl, c, clion
Published: 2019-09-02 11:04:35

Developing a PHP extension in C can be challenging compared to writing PHP code. You need to mind typing and make sure variables are initialized. There are a lot of macros and functions involved. And there is the dreaded segfault.

Working with a simple text editor can be frustrating. An IDE shows you where mistakes are made while typing. And the debugger can help to find the cause of segfaults and other issues.

If you're familiar with PHPStorm, the obvious IDE to use for extension development it CLion. Unfortunately, CLion is built around the CMake build tool, while PHP using automake.

Converting PHP to a CMake project is far from trivial. Luckily, we can make CLion behave relatively well with automake;

* Code completion and analysis
* Build and clean (as normal)
* Run tests with automatic (re)build
* Debugging

## Skelton extension

The Improved PHP Library [skeleton extension](https://github.com/improved-php-library/skeleton-php-ext) contains the necessary logic for editing in CLion as well as building on both *nix and Windows.

### CMakeList.txt

We don't want to build the project using CMake, but we still need an `add_library` command for CLion to acknowledge the source files. Using `___` as the name is an indication it should be ignored (you could also use `_ignore_`).

Instead, we add a custom target name `configure` which will run `phpize` and `./configure` for our extension.

The PHP source files need to be included. By executing `php-config` we can get the `PHP_SOURCE` directory that includes the header files.

It's common to do `#include "php.h"` rather than `#include "main/php.h"`. Therefore we also include the `main` subdirectory. The same for `Zend` and `TSRM`.

```bash
cmake_minimum_required(VERSION 3.8)
project(skeleton C)

add_compile_definitions(HAVE_SKELETON)

set(SOURCE_FILES php_skeleton skeleton.c)

execute_process (
        COMMAND php-config --include-dir
        OUTPUT_VARIABLE PHP_SOURCE
)
string(REGEX REPLACE "\n$" "" PHP_SOURCE "${PHP_SOURCE}")

message("Using source directory: ${PHP_SOURCE}")

include_directories(${PHP_SOURCE})
include_directories(${PHP_SOURCE}/main)
include_directories(${PHP_SOURCE}/Zend)
include_directories(${PHP_SOURCE}/TSRM)
include_directories(${PROJECT_SOURCE_DIR})

add_custom_target(configure
        COMMAND phpize && ./configure
        DEPENDS ${SOURCE_FILES}
        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})

add_library(___ EXCLUDE_FROM_ALL ${SOURCE_FILES})
```

## PHP versions

When building an extension, it's recommended to build and test it against multiple PHP versions. To install and use multiple PHP versions on your system you can use a tool like [phpbrew](http://phpbrew.github.io/phpbrew/).

### CMake profiles

In the project settings in CLion, we create multiple CMake profiles; one per PHP version.

Create a new profile and edit the environment settings. We'll modify `$PATH` so the correct `php`, `phpize` and `php-config` executables are used.

![CLion Profile env settings for PHP](https://thepracticaldev.s3.amazonaws.com/i/9nuou63c836igjtncgxn.png)

Copy the existing `PATH` entry and paste it as custom env var. Prepend the value with the path to the `bin` dir of the PHP version you want to use.

### PHP CLI symlink

CLion will only run configure with the selected PHP version. To run tests and debug we need to specify the path of the php cli executable. Instead of having to modify this every time, we can have autoconf create a symlink in the `build` subdir of this project.

```bash
AC_CONFIG_COMMANDS_POST([
  ln -s "$PHP_EXECUTABLE" build/php
])
```

### Build configure

Select 'configure' in build configurations with one of the PHP versions and build `Ctrl+F9`.

## make

CLion has an automake plugin. We won't use this to build the project. The plugin runs `make` as command and it doesn't work with the build functionality of CLion.

### Custom build target

In the project settings go to the "Custom Build Targets" tab and add a 'make' build target.

For `Build` click on the '...'  to create an external command to the `make` executable. Make sure the working directory is set to the project dir.

Duplicate the external command for `Clean`, adding `clean` as argument.

## Custom build configurations

### make

Edit the build configurations to add a new one, choosing "Custom Build Configuration". Name the configuration "make" and select `make` as `Target`.

As executable select `php` located in the `build` directory of the project (after you've run configure). Set program arguments to include your extension. It's recommended to use `-n` not to ignore the default php.ini and thus not load any other extension.

    -n -d "extension=modules/skeleton.so" "tests/smoke.php"

We need to change which file is run manually for debugging.

In the "Before launch" section add `Build`, so `make` is called before running `php` when there are changed source files.

![make build configuration for PHP in CLION](https://thepracticaldev.s3.amazonaws.com/i/lgvjv6fg8xqc75mma405.png)

### make test

PHP extension tests are written as [phpt file](https://qa.php.net/write-test.php) and run with `make test`. Writing tests is highly recommended and required for submitting the extension to PECL.

Duplicate the `make` build configuration and name it "make test". Change the executable to `make` (instead of `php`) and set the program arguments to `test`.

The working directory needs to be set to the project directory.

Add `NO_INTERACTION=1` to the environment variables to prevent the "Do you want to submit the test results?" question after running the tests.

### Clean toolbar button

It may help to add `Clean` (`Ctrl-Shift-F9`) to the toolbar.

![Toolbar config in CLION](https://thepracticaldev.s3.amazonaws.com/i/h48ak2yol4nzuwugi2oc.png)

## Optional Makefile targets

The following is optional but will help out during development.

### make info

Add a "Makefile" build configuration named "make info". Select the `Makefile` of the project and choose `info` as target.

### make clean-tests

When a test fails `.log`, `.diff`, etc files are created to aid in solving the issue. These files are automatically when the test passes.

To explicitly remove the files, we need to add a custom target to the makefile by creating a `Makefile.frag` file and adding

```bash
clean-tests:
	rm -f tests/*.diff tests/*.exp tests/*.log tests/*.out tests/*.php tests/*.sh
```

After, add a "Makefile" build configuration named "make clean-tests". Select the `Makefile` of the project and choose `clean-tests` as target.

### make mrproper

`make clean` removes the build files, but still leaves all automake artifacts. To remove all generated files add a `mrproper` target to the makefile via `Makefile.frag`;

```bash
mrproper: clean
	rm -rf autom4te.cache build modules vendor
	rm -f acinclude.m4 aclocal.m4 config.guess config.h config.h.in config.log config.nice config.status config.sub \
		configure configure.ac install-sh libtool ltmain.sh Makefile Makefile.fragments Makefile.global \
		Makefile.objects missing mkinstalldirs run-tests.php
```