Skip to main content

PHP Interop

On this page

Globals and constants#

Access PHP superglobals with php/ prefix and get:

(get php/$_SERVER "key") ; $_SERVER['key']
(get php/$GLOBALS "argv") ; $GLOBALS['argv']

PHP define constants accessed via php/CONSTANT_NAME:

(php/define "MY_SETTING" "My value") ; Calls PHP define('MY_SETTING', 'My value');
php/MY_SETTING ; => "My value"
PHP Coming from PHP?

The php/ prefix gives you direct access to PHP's global scope:

// PHP
$_SERVER['key']
$GLOBALS['argv']
MY_SETTING

// Phel
(get php/$_SERVER "key")
(get php/$GLOBALS "argv")
php/MY_SETTING

Note: Use Phel's immutable data structures when possible. Only use PHP arrays when you need to interop with PHP libraries that expect them.

Calling PHP functions#

Add php/ prefix to any PHP function name:

(php/strlen "test") ; => 4
(php/date "l")      ; => "Monday" (or whatever the current day is)
PHP Coming from PHP?

Any PHP function can be called by adding the php/ prefix:

// PHP
strlen("test");
date("l");
array_map($fn, $array);

// Phel
(php/strlen "test")
(php/date "l")
(php/array_map fn array)

However, Phel provides functional equivalents for many operations. For example, use (count "test") instead of (php/strlen "test") when working with Phel data structures.

Namespaced PHP functions use full path after php/. Three equivalent forms accepted (added in 0.37, last two are backslash-free):

(php/Foo\Bar\baz)      ; classic backslash form
(php/Foo.Bar/baz)      ; dot-separated, slash before fn name
(php/Foo.Bar.baz)      ; fully dot-separated

(php/Amp.trapSignal [(php/:: SIGINT) (php/:: SIGTERM)])

Capture into a Phel alias:

(def trap-signal php/\Amp.trapSignal)
(trap-signal [2 15])

Interop shorthands#

Terse forms that expand to verbose php/*. Use whichever reads better.

ShorthandExpands to
(ClassName. args)(php/new ClassName args)
`(new ClassName args)(php/new ClassName args)
(.method obj args)(php/-> obj (method args))
(.-field obj)(php/-> obj field)
(ClassName/method args)(php/:: ClassName (method args))
ClassName/MEMBER(php/:: ClassName MEMBER)
(ns my.module
  (:use DateTimeImmutable DateInterval))

(DateTimeImmutable. "2026-04-20")              ; constructor (preferred)
(.format (DateTimeImmutable.) "Y-m-d")          ; instance method
(.-s (DateInterval. "PT30S"))                  ; property
(DateTimeImmutable/createFromFormat "Y-m-d" "2026-04-20") ; static method
DateTimeImmutable/ATOM                         ; static constant

Class instantiation#

Three equivalent forms - prefer ClassName. for imported classes:

(ns my.module
  (:use DateTime DateTimeImmutable))

(DateTime.)              ; => DateTime instance (ClassName. shorthand)
(DateTime. "now")        ; => DateTime instance with arg
(new DateTime)           ; also valid
(php/new DateTime)       ; also valid

(php/new "\\DateTimeImmutable") ; instantiate from string (dynamic)
PHP Coming from PHP?
// PHP
new DateTime();
new DateTime("now");
new \DateTimeImmutable();

// Phel - preferred shorthand
(DateTime.)
(DateTime. "now")
(DateTimeImmutable.)

Import classes with :use to use the short ClassName. form without repeating the namespace.

Method and property call#

(php/-> object (methodname expr*))
(php/-> object property)

Calls method or accesses property. Both methodname and property must be symbols, not evaluated values.

Chain multiple in one php/->. Each element evaluates on result of previous, enabling fluent chains or nested property access.

(ns my.module
  (:use DateInterval)
  (:use DateTimeImmutable)
  (:use stdClass))

(def di (DateInterval. "PT30S"))

(.format di "%s seconds")          ; => "30 seconds"  (.method shorthand)
(php/-> di (format "%s seconds"))  ; same, verbose form
(.-s di)                           ; => 30  (.-prop shorthand)

;; Chain multiple calls:
;; (new DateTimeImmutable("2024-03-10"))->modify("+1 day")->format("Y-m-d")
(-> (DateTimeImmutable. "2024-03-10")
    (.modify "+1 day")
    (.format "Y-m-d"))

;; php/-> also works and is required for chains mixing methods and properties:
(php/-> user profile (getDisplayName))

;; Nested property access:
(def address (stdClass.))
(def user    (stdClass.))
(php/oset (php/-> address city) "Berlin")
(php/oset (php/-> user address) address)
(php/-> user address city) ; => "Berlin"
PHP Coming from PHP?

The php/-> operator is similar to PHP's -> but allows chaining in a more functional style:

// PHP
$di->format("%s seconds");
$di->s;
(new DateTimeImmutable("2024-03-10"))->modify("+1 day")->format("Y-m-d");
$user->profile->getDisplayName();

// Phel - shorthand forms
(.format di "%s seconds")
(.-s di)
(-> (DateTimeImmutable. "2024-03-10") (.modify "+1 day") (.format "Y-m-d"))
(php/-> user profile (getDisplayName))   ; mixed chains need php/->

Method calls: (.method obj args) shorthand or (php/-> obj (method args)). Property access: (.-prop obj) or (php/-> obj prop). Mixed chains (method + property in one expression) use php/-> directly.

Clojure Coming from Clojure?

The php/-> operator is inspired by Clojure's thread-first macro ->, but specifically designed for PHP object method chaining.

Static method and property#

(php/:: class (methodname expr*))
(php/:: class property)

Same as above, but static.

(ns my.module
  (:use DateTimeImmutable))

DateTimeImmutable/ATOM                                     ; => "Y-m-d\TH:i:sP"  (shorthand)
(php/:: DateTimeImmutable ATOM)                            ; verbose form

(DateTimeImmutable/createFromFormat "Y-m-d" "2020-03-22") ; shorthand
(php/:: DateTimeImmutable (createFromFormat "Y-m-d" "2020-03-22")) ; verbose
PHP Coming from PHP?

The php/:: operator is equivalent to PHP's :: for static method and property access:

// PHP
DateTimeImmutable::ATOM;
DateTimeImmutable::createFromFormat("Y-m-d", "2020-03-22");

// Phel - shorthand forms
DateTimeImmutable/ATOM
(DateTimeImmutable/createFromFormat "Y-m-d" "2020-03-22")

Set object properties#

(php/oset (php/-> object property) value)
(php/oset (php/:: class property) value)

Set value on class/object property.

(def x (stdclass.))
(php/oset (php/-> x name) "foo")
PHP Coming from PHP?

php/oset is the Phel equivalent of PHP's property assignment:

// PHP
$x = new stdClass();
$x->name = "foo";

// Phel
(def x (stdclass.))
(php/oset (php/-> x name) "foo")

Note: This mutates the PHP object. When possible, use Phel's immutable data structures instead.

Get PHP array value#

(php/aget arr index)

Equivalent: arr[index] ?? null.

(php/aget ["a" "b" "c"] 0) ; Evaluates to "a"
(php/aget (php/array "a" "b" "c") 1) ; Evaluates to "b"
(php/aget (php/array "a" "b" "c") 5) ; Evaluates to nil
PHP Coming from PHP?

php/aget safely accesses PHP array elements:

// PHP
$arr[0] ?? null;
$arr[1] ?? null;
$arr[5] ?? null;  // Returns null

// Phel
(php/aget arr 0)
(php/aget arr 1)
(php/aget arr 5)  ; Returns nil

Important distinction:

  • Use php/aget for PHP arrays (mutable)
  • Use get for Phel data structures (immutable vectors, maps)

Get nested PHP array value#

(php/aget-in arr path)

Resolves nested values via a sequence of keys/indexes. path is a sequential collection (e.g. vector). Missing step returns nil.

(def users
  (php/array
    "users"
    (php/array
      (php/array "name" "Alice")
      (php/array "name" "Bob"))))

(php/aget-in users ["users" 1 "name"]) ; Evaluates to "Bob"

(php/aget-in
    (php/array "meta" (php/array "status" "ok"))
    ["meta" "status"]) ; Evaluates to "ok"

(php/aget-in
    (php/array "meta" (php/array "status" "ok"))
    ["meta" "missing"]) ; Evaluates to nil
PHP Coming from PHP?

php/aget-in provides safe nested array access:

// PHP - manual nested access with null coalescing
$users['users'][1]['name'] ?? null;
$data['meta']['status'] ?? null;
$data['meta']['missing'] ?? null;

// Phel - clean path-based access
(php/aget-in users ["users" 1 "name"])
(php/aget-in data ["meta" "status"])
(php/aget-in data ["meta" "missing"])  ; Returns nil safely

This is similar to Phel's get-in for immutable data structures, but specifically for PHP arrays.

Set PHP array value#

(php/aset arr index value)

Equivalent: arr[index] = value.

PHP Coming from PHP?

php/aset mutates a PHP array in place:

// PHP
$arr[0] = "value";

// Phel
(php/aset arr 0 "value")

Important: This mutates the array. For immutable operations, use Phel's assoc on Phel data structures instead.

Set nested PHP array value#

(php/aset-in arr path value)

Creates or updates nested entries. Missing intermediate arrays are created.

(def data (php/array))
(php/aset-in data ["user" "profile" "name"] "Charlie")
(php/aget-in data ["user" "profile" "name"]) ; Evaluates to "Charlie"
;; Equivalent to $data['user']['profile']['name'] = 'Charlie';
PHP Coming from PHP?

php/aset-in creates nested structures automatically:

// PHP - manual nested array creation
$data = [];
$data['user']['profile']['name'] = 'Charlie';

// Phel - automatic path creation
(def data (php/array))
(php/aset-in data ["user" "profile" "name"] "Charlie")

This is the mutable counterpart to Phel's assoc-in for immutable data structures.

Append PHP array value#

(php/apush arr value)

Equivalent: arr[] = value.

PHP Coming from PHP?

php/apush appends to a PHP array:

// PHP
$arr[] = "new value";

// Phel
(php/apush arr "new value")

For immutable operations, use conj on Phel vectors instead.

Unset PHP array value#

(php/aunset arr index)

Equivalent: unset(arr[index]).

PHP Coming from PHP?

php/aunset removes an element from a PHP array:

// PHP
unset($arr[0]);

// Phel
(php/aunset arr 0)

For immutable operations, use dissoc on Phel maps instead.

Unset nested PHP array value#

(php/aunset-in arr path)

Removes nested entry. Parent arrays remain untouched even if empty after.

(def data (php/array "user" (php/array "profile" (php/array "name" "Dora"))))
(php/aunset-in data ["user" "profile" "name"])
(php/aget-in data ["user" "profile" "name"]) ; Evaluates to nil
;; Equivalent to unset($data['user']['profile']['name']);
PHP Coming from PHP?

php/aunset-in removes nested array elements:

// PHP
unset($data['user']['profile']['name']);

// Phel
(php/aunset-in data ["user" "profile" "name"])

Parent arrays remain intact even if they become empty after the unset.

__DIR__, __FILE__, *file*#

PHP magic constants __DIR__ and __FILE__ work but expand at PHP compile, pointing to the generated PHP file under .phel/cache.

For the original Phel source path, use *file* (absolute path of current Phel file). Combine with php/dirname for the source dir.

(println __DIR__)  ; Directory name of the generated PHP file
(println __FILE__) ; Filename of the generated PHP file

(println (php/dirname *file*)) ; Directory of the original Phel file
(println *file*)               ; Absolute path of the original file
PHP Coming from PHP?

Important distinction:

// PHP magic constants
__DIR__   // Points to .phel/cache directory (generated PHP)
__FILE__  // Points to cached .php file

// Phel special var
*file*    // Points to your actual .phel source file

Use *file* when you need to reference the original Phel source location, such as for loading resources relative to your source code.

Calling Phel from PHP#

Useful for integrating Phel into existing PHP apps. Load the Phel namespace after autoload.php.

Example: using-exported-phel-function.php

<?php

use Phel\Phel;
use PhelGenerated\CliSkeleton\Modules\AdderModule;

$projectRootDir = dirname(__DIR__);

require $projectRootDir . '/vendor/autoload.php';

Phel::run($projectRootDir, 'cli-skeleton.modules.adder-module');

$adder = new AdderModule();
$result = $adder->adder(1, 2, 3);

echo 'Result = ' . $result . PHP_EOL;

Two ways: manually, or via the export command.

Manually#

PhelCallerTrait calls any Phel function from a PHP class. Inject the trait, call callPhel.

<?php
use Phel\Interop\PhelCallerTrait;

class MyExistingClass {
  use PhelCallerTrait;

  public function myExistingMethod(...$arguments) {
    return $this->callPhel(
        'my.phel.namespace', 
        'phel-function-name', 
        ...$arguments
    );
  }
}

Using the export command#

phel export generates a wrapper class for all Phel functions marked export.

Add config to phel-config.php first:

<?php
return (new \Phel\Config\PhelConfig())
    ->withExportFromDirectories(['src'])
    ->withExportNamespacePrefix('PhelGenerated')
    ->withExportTargetDirectory('src/PhelGenerated')
;

Option details: Configuration.

Mark a function exported with metadata:

(defn my-function
  {:export true}
  [a b]
  (+ a b))

phel export then generates a wrapper class in the target dir (here src/PhelGenerated). Use it from PHP to call Phel functions.