Skip to content

Instantly share code, notes, and snippets.

@decima
Last active September 29, 2018 18:25
Show Gist options
  • Save decima/02020f7cf71e32da12def756ac1d6ab3 to your computer and use it in GitHub Desktop.
Save decima/02020f7cf71e32da12def756ac1d6ab3 to your computer and use it in GitHub Desktop.
First try to create generic types in php :3
<?php
// This is the hidden part ;-)
trait Generic
{
private $_T = null;
private $__generic_nullable = false;
private $__originalObject = null;
/**
* @param $generic_types
* @return self
*/
public function _T($generic_type, $nullable = false)
{
$this->_T = $generic_type;
$this->__generic_nullable = $nullable;
return new class($this->_T, $this, $nullable)
{
use Generic;
private $_childT;
private $_childObject;
private $_childNullable;
public function __construct($__type, &$__parent, $__nullable)
{
$this->_childT = $__type;
$this->_childObject = $__parent;
$this->_childNullable = $__nullable;
}
public function __get($name)
{
return $this->_childObject->$name;
}
public function __set($name, $value)
{
return $this->_childObject->$name = $value;
}
public function __isset($name)
{
return isset($this->_childObject->$name);
}
public function __unset($name)
{
unset($this->_childObject->$name);
}
public function __toString()
{
return (string)$this->_childObject;
}
public function __invoke(...$args)
{
return $this->__call("__invoke", $args);
}
/**
* @param $name
* @param $arguments
* @return {$this->_childT}|null
*/
public function __call($name, $arguments)
{
$name = "T_" . $name;
$reflectionObject = new \ReflectionObject($this->_childObject);
$returnType = $reflectionObject->getMethod($name)->getReturnType();
$classImplements = array_merge([$this->_childT], class_implements($this->_childT));
$returnTypeClass = $returnType->getName();
if (in_array($returnTypeClass, array_values($classImplements))) {
if (method_exists($this->_childObject, $name)) {
$response = call_user_func_array([$this->_childObject, $name], array_merge([$this->_childT], $arguments));
if (is_object($response) && ($given = get_class($response)) !== ($expected = $this->_childT)) {
throw new \Exception("expected $expected as return, $given got.");
}
if ($response === null && !$this->_childNullable) {
throw new \Exception("unexpected null value.");
}
return $response;
}
}
throw new \Exception("return type invalid");
}
};
}
}
//this is an example of usage :-)
interface Food
{
public static function requires(): array;
}
class Cookie implements Food
{
public static function requires(): array
{
return ["chocolate", "butter"];
}
}
class CookedFish implements Food
{
public static function requires(): array
{
return ["rawfish"];
}
}
class Cooker
{
use Generic;
private $ingredients = ["rawfish" => 10, "chocolate" => 5, "butter" => 4];
public function T_cook($T, $useHoven = false): ?Food
{
foreach (($T)::requires() as $neededIngredient) {
if (!isset($this->ingredients[$neededIngredient]) || $this->ingredients[$neededIngredient] < 1) {
return null;
} else {
$this->ingredients[$neededIngredient]--;
}
}
return (new $T());
}
public function getIngredients()
{
return $this->ingredients;
}
}
$cooker = new Cooker();
$cookie = $cooker->_T(Cookie::class, true)->cook(true);
$rawfish = $cooker->_T(CookedFish::class, true)->cook(true);
var_dump($cookie);
var_dump($rawfish);
var_dump($cooker->getIngredients());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment