Unified symbol tables for properties and methods in PHP
Tags: php, oop, functional, kittens
Published: 2018-01-12 11:48:37

---
title: Unified symbol tables for properties and methods in PHP
published: true
description: Let's tear down the differences between them!
tags: #php #oop #functional #kittens
---

![](https://thepracticaldev.s3.amazonaws.com/i/fve1zcq0afc2j1tvrl3h.png)

## Introduction

If you ever worked with the `callable` type, you probably already know its quirks ([... and even more quirks](https://wiki.php.net/rfc/consistent_callables)). I bet you hate them as much as I do. In short, **a callable that once passed a `callable` type declaration, not necessarily will pass all `callable` type declarations** ([example](https://3v4l.org/a27NX)). I think the `callable` type was a bad idea altogether, and I think modern PHP code can be much better than that.

`Closure`s type declarations have no context-dependent behavior. A `Closure` reference is actually callable everywhere.

What we all should be doing, instead, is:

```php
<?php
//  1- Retrieve the Closure from a callable just once
//  2- Only pass around Closures and always type-hint for Closure
//     ...
// 42- Profit!
public function bar(Closure $in): Closure{
    $in();
    return Closure::fromCallable([$this, "method"]);
}
```

## Making it nicer with _Symbola_

I wrote a small tool that improves the way we access object members. You can immediately install it and try it out using composer (`composer require netmosfera/symbola`) or have a look at it on [github](https://github.com/Netmosfera/Symbola).

Here is what it does:

```
<?php

use Netmosfera\Symbola\Symbola;

class Bar
{
    use Symbola;

    public $myProperty;

    function __construct(Closure $p){
        $this->myProperty = $p;
    }

    public function myMethod(){
        echo "myMethod() called!\n";
    }    
}

$p = function(){
    echo "myProperty() called!\n";
};

$bar = new Bar($p);

// Feature #1:
// Call a property like a method:
$bar->myProperty(); // myProperty() called!

// Feature #2:
// Reference a method like a property:
$methodReference = $bar->myMethod;
$methodReference(); // method() called!
```

## Visibility support
Properties can be called only from compatible scopes, and method handles can be retrieved only from compatible scopes:

```php
<?php

use Netmosfera\Symbola\Symbola;

class Bar
{
    use Symbola;

    private $myProperty;

    function __construct(){
        $this->myProperty = function(){
            echo "myProperty() called!\n";
        };
    }

    private function myMethod(){
        echo "myMethod() called!\n";
    }

    public function callProperty(){
        $this->myProperty();
    }

    public function getMethod(): Closure{
        return $this->myMethod;
    }
}

$bar = new Bar();

//------------------------------------------------------------------------------------------

try{
    // The property is private, it cannot be called publicly:
    $bar->myProperty();
} catch(Error $e){
    echo $e->getMessage() . "\n";
    // Error: Referenced the either undefined or
    // non-public object member `Bar::myProperty`
}

//------------------------------------------------------------------------------------------

try{
    // The method is private, it cannot be referenced publicly:
    $methodReference = $bar->myMethod;
} catch(Error $e){
    echo $e->getMessage() . "\n";
    // Error: Referenced the either undefined or
    // non-public object member `Bar::myMethod`
}

//------------------------------------------------------------------------------------------

// The property can be called privately, however:
$bar->callProperty(); // myProperty() called!

// Similarly, the method can be referenced privately,
// and the obtained reference is free to be passed to
// other scopes:
$methodReference = $bar->getMethod();
$methodReference(); // myMethod() called!
```
Obviously, `protected` is also supported; a protected method can be referenced only within the class' hierarchy, and a protected property can be called only within the class' hierarchy.

## Equatable `Closure`s
A problem that `Closure::fromCallable()` has, is that the returned objects cannot be compared for equality (latest PHP version tested is 7.2):

```php
<?php

class A{ function bar(){} }
$a = new A;
$c1 = Closure::fromCallable([$a, "bar"]);
$c2 = Closure::fromCallable([$a, "bar"]);
assert($c1 === $c2); // Fail!
```

But _Symbola_ solves this by keeping a handle for each of the created `Closure`s, which is then reused when needed:

```php
<?php

use Netmosfera\Symbola\Symbola;

class A{ use Symbola; function bar(){} }
$a = new A;
$c1 = $a->bar;
$c2 = $a->bar;
assert($c1 === $c2); // Works!
```

The handles for `Closure`s are kept in the object itself and they are garbage-collected when the object is destroyed, thus naturally limiting the number of objects created in the program.

## What's `$this` set to?

`$this` in property-Closures is not rebound to the host class' `$this`; if this is needed, it must be performed manually using `Closure::bind()`.

```php
<?php

use Netmosfera\Symbola\Symbola;

class Baz
{
    use Symbola;

    public $qux;

    function __construct($qux){
        $this->qux = $qux;
    }
}

class Foo
{
    function getClosure(): Closure{
        return function(){
            assert($this instanceof Foo);
        };
    }
}

$foo = new Foo();
$baz = new Baz($foo->getClosure());
$baz->qux(); // $this in qux() is $foo, not $baz
```

## IDE support
One of the worst problems with `callable` is the lack of static analysis. IDEs really struggle distinguishing between strings/arrays and `callables`.

```php
<?php

// I look like a simple string, but trust me,
// I'm actually a callable ¯\_(ツ)_/¯ 
$foo = "baz";

// ...

// ಠ_ಠ
$foo();
```

With _Symbola_, magic functionality can be annotated using phpdoc's `@property` and `@method`:

```php
<?php

use Netmosfera\Symbola\Symbola;

/**
 * @property Closure $bar
 * @method int qux(float $a)
 */
class Baz
{
    use Symbola;

    public $qux;

    function __construct(){
        $this->qux = function(float $a): int{};
    }

    function bar(){}
}
```

Now, that's far from being nice, but... it's something. Refactoring works, static analysis works (mostly), search works.

![Result in PHPStorm](https://i.imgur.com/MSp1pER.png)


## What's missing

- Function-scope `static` variables don't work, but nobody uses them, right? They are a mess regardless.

- Static methods and properties are also not supported. I don't think it's important to add support only for `__callStatic` given the lack of a `__getStatic` magic method.

- It is not possible to create `Closure`s out of `parent::` methods. I might add this at some point; e.g. `Symbola::parent("method")` as alternative to `Closure::fromCallable("parent::method")`.

- Probably something else, let me know what :-P

## Installation

```
composer require netmosfera/symbola
```

[Symbola on GitHub](https://github.com/Netmosfera/Symbola)

I hope I convinced you to try it out, I bet you will like it!
Thanks for reading, and let me know what you think in the comments.