<?php
class Collection
{
private $items;
private static $macros = [];
public static function macro($name, callable $macro)
{
static::$macros[$name] = Closure::fromCallable($macro);
}
public static function mixin($class)
{
foreach (get_class_methods($class) as $method) {
static::macro($method, $class->$method());
}
}
public function __construct(array $items)
{
$this->items = $items;
}
public function map(callable $callback)
{
return new static(array_map($callback, $this->items));
}
public function all()
{
return $this->items;
}
public function exampleClosure()
{
return function () {
return $this->items;
};
}
public function __call($method, $args)
{
return static::$macros[$method]->bindTo($this, static::class)(...$args);
}
public static function __callStatic($method, $args)
{
return static::$macros[$method]->bindTo(null, static::class)(...$args);
}
}
Collection::macro("flatMap", function (callable $callback) {
$tmp = [];
foreach ($this->items as $item) {
$tmp = array_merge($tmp, $callback($item));
}
return new static($tmp);
});
$items = new Collection(["a", "b", "c"]);
$items = $items->flatMap(function ($item) {
return [$item, $item];
});
if ($items->all() === ["a", "a", "b", "b", "c", "c"]) {
echo "it works";
}
// This is the thing that is deprecated and causes a warning:
// Here we're removing $this from a closure that isn't static
// All non-static closures will have a $this in PHP 8.x when defined in a method
try {
var_dump(
$items->exampleClosure()->bindTo(null)()
);
} catch (\Throwable $e) {
// We'll get an error about using $this is a non-object context
// This will no longer be possible after PHP 7.4
var_dump($e);
}
// You can definitely still do this:
class UnrelatedClass { private $items = ["foo", "bar"]; }
var_dump(
$items->exampleClosure()->bindTo(new UnrelatedClass, UnrelatedClass::class)()
);
// The only thing that could cause problems is:
// methods meant to be called statically in a mixin that were not defined with static:
class SomeMixin
{
public function range()
{
return function ($start, $end) {
return new static(range($start, $end));
};
}
public function staticRange()
{
return static function ($start, $end) {
return new static(range($start, $end));
};
}
}
Collection::mixin(new SomeMixin());
// This will error because:
// 1. the above is not defined with static
// 2. The closure is written inside a class
var_dump(Collection::range(0, 10));
// The following will not because the closure was defined with static
var_dump(Collection::staticRange(0, 10));
// Basically the change prevents the creation of methods and closures that can be used as instance and static methods at the same time by dynamically checking $this inside the function.