Omawiamy klasę znalezioną na GitHubie, która pozwala mieć literały obiektów w PHP (jak w JS, razem z metodami). Do dzieła.

Ok, literał tworzymy w ten sposób:

$object = new ObjectLiteral([
    "name" => "Fido",
    "barks" => true,
    "age" => 10,
    'say' => function ($self) {
        if ($self->barks) {
            return "Woof";
        }
        return "";
    },
    'toys' => array('bear', 'bone')
]);

echo $object->say();

var_dump($object);
echo "<br>";
print_r($object->toys);

Zobaczmy na samą klasę, na jej konstruktor:

class ObjectLiteral extends \stdClass
{
    public function __construct($value = null)
    {
        if (is_null($value) || $value == '') {
            return;
        }

        if (is_array($value)) {
            $object = (object)$value;
            $this->importPropertiesFrom($object);
            return;
        }

        if (is_string($value)) {
            $object = json_decode($value);
            if (!is_null($object)) {
                $this->importPropertiesFrom($object);
                return;
            }
        }

        throw new \InvalidArgumentException('Object can not be build from value ' . var_export($value, true));
    }

   //(...)
}

Jak widać, klasa albo przyjmuje null/pusty napis i nic nie zwraca, albo przyjmuje tablicę, rzutuje do object i przerzuca do importPropertiesFrom, albo jeśli string, to stara się zamienić go na obiekt przez json_decode.

Ok, teraz metoda call:

 public function __call($name, $args)
    {
        if (!is_callable($this->$name)) {
            $this->triggerError("Not callable property: $name", E_ERROR);
        }

        array_unshift($args, $this);
        return call_user_func_array($this->$name, $args);
    }

    protected function triggerError($errorMsg, $errorType = E_USER_NOTICE)
    {
        $this->triggerError($errorMsg, $errorType);
    }

Metoda call jest wywoływana, gdy ktoś zawoła metodę, która nie istnieje w klasie (w klasie object literal). Sprawdzamy, czy klasa ma takie property (ustawione w importPropertiesFrom) i czy to property jest callable.

Dodajemy this do argumentów i wołamy metodę.

Teraz metoda importPropertiesFrom:

 private function importPropertiesFrom($object)
    {
        foreach (get_object_vars($object) as $key => $value) {
            if (is_scalar($value) || is_callable($value)) {
                $this->$key = $value;
                continue;
            }
            $this->$key = new ObjectLiteral($value);
        }
    }
}

Ok, czyli przekazujemy do niej tablicę rzutowaną do object. Tam przez get_object_vars iterujemy i jeśli wartość to scalar (typ prosty) lub callable to przypisujemy pod kluczem, jeżeli nie tworzymy rekurencyjnie nowy ObjectLiteral.

Jedno, co mi się tu nie podoba to brak możliwości obsługi tablic. Zobaczmy na ten kod:

$object = new ObjectLiteral([
    "name" => "Fido",
    "barks" => true,
    "age" => 10,
    'say' => function ($self) {
        if ($self->barks) {
            return "Woof";
        }
        return "";
    },
    'toys' => array('bear', 'bone')
]);

echo $object->say();

var_dump($object);
echo "<br>";
print_r($object->toys);

Tutaj toys to będzie ObjectLiteral. Zupełnie niepotrzebnie, my chcemy tablicę. Z drugiej strony musimy pamiętać, że tablice to nie typy proste.

Ok, ja sobie tę klasę zmodyfikowałem tak, aby tablica asocjacyjna była traktowana jako literał obiektu wewnątrz literału obiektu, zaś tablica-lista oznaczała nowe pole tablicowe:

private function importPropertiesFrom($object)
    {
        foreach (get_object_vars($object) as $key => $value) {
            if (is_scalar($value) || is_callable($value)) {
                $this->$key = $value;
                continue;
            }
            else if(array_is_list($value)){
                $this->$key = [...$value];
                continue;
            } 
            $this->$key = new ObjectLiteral($value);
        }
    }
}

I tak w banalnie prosty sposób można osiągnąć literały obiektów w PHP.