PHP is a server-side scripting language.

## Installation

Refer to the Dockerfiles at https://git.steamr.com/docker/php-fpm if you wish to install PHP-FPM manually.

## Interpreter

The PHP interpreter consists of two parts: The PHP Core and the Zend Engine.

The PHP core handles communication with bindings, SAPIs, file and network IO, and handling of the many PHP extensions.

The Zend Engine is responsible for interpreting the PHP code into bytecode. The interpretation process involves splitting the code into tokens using a tokenizer (initiated by PHP's core function `zend_compile_file()`) and converting these tokens into bytecode. `zend_compile_string()` can also be used to compiles a string and is used for functions such as `eval()`.

The PHP bytecode is executed by the PHP's virtual machine. The VM has a virtual CPU with its own set of instructions. Execution is initiated by passing an array of opcodes to `zend_execute()`.

### Bytecode

The register-based bytecode of PHP consists of opcodes, constants, variables, and meta information. PHP has around 200 different opcodes that cover all existing language constructs. (see https://www.php.net/manual/en/internals2.opcodes.php).

Each opcode conforms to the `zend_op` data structure and has:

1. an opcode number (`znode_uchar`) used to find the corresponding opcode handler in a lookup table.
2. two operand parameters (value as `znode_op`, type as `znode_uchar`)
3. a result operand to store return values (`znode_op`, type as `znode_uchar`)

5 types:

1. IS_UNUSED: Operand not used
2. IS_CONST: Constants are literals in code
3. IS_TMPVAR: Temporary variable, as result of `\$foo+\$bar` for example. `~0`, where 0 would be the first, 1 the second, an so on.
4. IS_VAR: Non-simple variables or variables requiring a lookup. `\$n`.
5. IS_CV: Compiled variable to save PHP from looking up variables in hash table. `!n`, where n is the offset to the compiled-variable array.

Index to retrieve the handler address in the lookup table is: ${\displaystyle i=opcode_{n}umber*25+op1_{t}ype*5+op2_{t}ype}$

#### Encoders

• ionCube
• Zend Guard
• SourceGuardian

```<?php //0xxxx
*load extension / print message code*
binary encoded data using custom Base64
```

The file is checked using Adler32 and binary contents are encoded using a custom Base64 format.

The ionCube loader hooks `zend_compile_file()` and tests for `<?php //` at the beginning of an executed PHP file. The hexadecimal value immediately following this specifies the size of the fallback code which the loader skips. The binary encoded data is then loaded and parsed by the ionCube extension.

## Stuff

### Error Reporting

To always show all errors and warnings:

```error_reporting(E_ALL);
ini_set('display_errors', 1);
```

In more detail:

```// Turn off all error reporting
error_reporting(0);

// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// Reporting E_NOTICE can be good too (to report uninitialized
// variables or catch variable name misspellings ...)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// Report all errors except E_NOTICE
// This is the default value set in php.ini
error_reporting(E_ALL ^ E_NOTICE);

// Report all PHP errors (see changelog)
error_reporting(E_ALL);

// Report all PHP errors
error_reporting(-1);

// Same as error_reporting(E_ALL);
ini_set('error_reporting', E_ALL);
```

### UTF-8 to hex

There is basically NO good way.

## Troubleshooting

### HTTP POST Array Empty

I had an issue where a PHP POST array was empty when a POST request was made. It turns out that PHP now needs the `Content-Type` header set for it to be populated regardless of the HTTP method being used.

In detail, when the following request was made, `\$_POST` was empty:

```POST /print HTTP/1.1
Connection: Keep-Alive
User-Agent: WebSlipPrinting
Content-Length: 56
Host: 172.20.1.151

```

The data though was still sent since `\$_SERVER['CONTENT_LENGTH']` was 56 as in the original request. The data was also present in the `php://input` stream. ie:

```var_dump(file_get_contents("php://input"));
# Returned:
```

Adding the `Content-Type` value to the header fixes the issue, with the final request as:

```POST /print HTTP/1.1
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
User-Agent: WebSlipPrinting
Content-Length: 56
Host: 172.20.1.151

```

This may have something to do with the `enable_post_data_reading` option in PHP. From the config file:

```647 ; Whether PHP will read the POST data.
648 ; This option is enabled by default.
649 ; Most likely, you won't want to disable this option globally. It causes \$_POST
650 ; and \$_FILES to always be empty; the only way you will be able to read the
651 ; POST data will be through the php://input stream wrapper. This can be useful
652 ; to proxy requests or to process the POST data in a memory efficient fashion.
```

### PDOException : could not find driver

You need to have the pdo driver installed. In my case, I was trying to use the pdo-mysql connector which wasn't compiled with PHP. To fix this, install the PDO driver or compile it manually with:

```--with-pdo-<driver>
```

In my case, I needed mysql:

```--with-pdo-mysql
```

### `mail()` issues

If `mail()` returns `sh: -t: command not found` when attempting to run the `mail()` function, you probably did not set the `sendmail_path` variable in your `php.ini`.

Change:

```; sendmail_path =
```

To:

```sendmail_path = /usr/sbin/sendmail -t -i
```

If you get `Recipient names must be specified` when calling `mail()`, you need to set the `-t` flag in `sendmail_path` in `php.ini`.