Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: juzna/php-enhancer
base: c40a4d855d
...
head fork: juzna/php-enhancer
compare: 08e98b4eb1
Checking mergeability… Don't worry, you can still create the pull request.
  • 2 commits
  • 5 files changed
  • 0 commit comments
  • 1 contributor
View
249 examples/03-generics/GenericsEnhancer.php
@@ -33,6 +33,12 @@ class GenericsEnhancer implements \Enhancer\IEnhancer
*/
private $functionParameterHint = array();
+ /** @var string[] names of type arguments of class being processed now */
+ private $currentTypeArgs;
+
+ /** @var array shortName => fullName */
+ private $uses;
+
/**
@@ -41,9 +47,14 @@ class GenericsEnhancer implements \Enhancer\IEnhancer
*/
public function enhance($code)
{
+ // clean
+ $this->currentTypeArgs = NULL;
+ $this->uses = array('' => '');
+ $this->namespace = '';
+
+
$this->parser = new Enhancer\Utils\PhpParser($code);
- $this->namespace = $s = '';
- $uses = array('' => '');
+ $s = '';
while (($token = $this->parser->fetch()) !== FALSE) {
if ($this->parser->isCurrent(T_NAMESPACE)) {
@@ -65,7 +76,7 @@ public function enhance($code)
$as = $this->parser->fetch(T_AS)
? $this->parser->fetch(T_STRING)
: substr($class, strrpos("\\$class", '\\'));
- $uses[strtolower($as)] = $class;
+ $this->uses[strtolower($as)] = $class;
} while ($this->parser->fetch(','));
$this->parser->fetch(';');
@@ -79,7 +90,9 @@ public function enhance($code)
$s .= "\\GenericsRegistry::newInstance('" . $this->fullClass($className) . "'";
if ($generics = $this->fetchGenericParameter()) {
- $s .= ', array(\'' . implode('\', \'', $generics) . '\')';
+ $s .= ', ' . $this->createCodeGenericParameter($generics);
+ } else {
+ $s .= ', NULL'; // no generics
}
if ($this->parser->isNext(';')) { // without parentheses
@@ -100,7 +113,7 @@ public function enhance($code)
$token . $this->parser->fetchAll(T_WHITESPACE), NULL, NULL, NULL,
);
$classDef[1] .= $className = $this->parser->fetchAll(T_STRING, T_NS_SEPARATOR);
- $generics = $this->fetchGenericParameter();
+ $generics = $this->currentTypeArgs = $this->fetchGenericParameter();
if ($this->parser->isNext(T_EXTENDS)) {
$classDef[2] .= $this->parser->fetch() . $this->parser->fetchAll(T_STRING, T_NS_SEPARATOR);
}
@@ -111,6 +124,10 @@ public function enhance($code)
$classDef[3] .= (!$classDef[3] ? ' implements ' : ', ') . '\GenericType';
}
+ $classDef[4] = $this->parser->fetchAll('{'); // start class
+
+ $classDef[5] = $generics ? 'public function getParametrizedType($parameterName) { return "TODO"; }' : '';
+
$s .= '\\GenericsRegistry::registerClass(\'' .
$this->fullClass($className) . '\', array(\'' .
implode('\', \'', $generics) .
@@ -137,13 +154,17 @@ public function enhance($code)
}
}
- dump($s);
+// dump($s);
return $s;
}
+ /***************** parsing *****************j*d*/
+
+
+
/**
* @return array
*/
@@ -170,6 +191,31 @@ private function fetchGenericParameter()
+ /***************** helper code generating *****************j*d*/
+
+
+
+ private function createCodeGenericParameter($generics)
+ {
+ $parts = array();
+ foreach ($generics as $v) {
+ if ($this->currentTypeArgs && in_array($v, $this->currentTypeArgs)) { // type argument
+ $parts[] = "\\GenericsRegistry::resolveTypeArgument(\$this, '$v')";
+
+ } else { // just a simple class name
+ $parts[] = "'$v'";
+ }
+ }
+
+ return 'array(' . implode(', ', $parts) . ')';
+ }
+
+
+
+ /***************** utils *****************j*d*/
+
+
+
/**
* @param string $className
* @return string
@@ -177,8 +223,8 @@ private function fetchGenericParameter()
private function fullClass($className)
{
$segment = strtolower(substr($className, 0, strpos("$className\\", '\\')));
- $full = isset($uses[$segment])
- ? $uses[$segment] . substr($className, strlen($segment))
+ $full = isset($this->uses[$segment])
+ ? $this->uses[$segment] . substr($className, strlen($segment))
: $this->namespace . '\\' . $className;
return str_replace('\\', '\\\\', ltrim($full, '\\'));
}
@@ -212,12 +258,12 @@ class GenericsRegistry
{
/**
- * @var array
+ * @var array oid => typeArgumentName => TypeValue
*/
private static $instances = array();
/**
- * @var array
+ * @var array className => index => TypeArgument
*/
private static $classes = array();
@@ -225,12 +271,15 @@ class GenericsRegistry
/**
* @param string $className
- * @param array $genericTypes
+ * @param string[]|TypeValue[] $rawTypeValues
*
* @return mixed
*/
- public static function newInstance($className, $genericTypes /*, $args */)
+ public static function newInstance($className, array $rawTypeValues = null /*, $args */)
{
+ $refl = new ReflectionClass($className); // autoloads the class
+ $typeValues = self::resolveTypeValues($className, $rawTypeValues); // check types
+
$args = func_get_args();
array_shift($args); // className
array_shift($args); // genericTypes
@@ -238,12 +287,12 @@ public static function newInstance($className, $genericTypes /*, $args */)
echo "LOG: Creating instance of '$className' with $cnt arguments\n";
- $refl = new ReflectionClass($className);
$obj = $refl->newInstanceArgs($args);
echo "LOG: ... done\n";
- self::$instances[spl_object_hash($obj)] = $genericTypes;
+ self::$instances[spl_object_hash($obj)] = $typeValues;
+
return $obj;
}
@@ -251,7 +300,7 @@ public static function newInstance($className, $genericTypes /*, $args */)
/**
* @param object $object
- * @return array
+ * @return TypeValue[] typeArgumentName => TypeValue
*/
public static function getParametrizedTypesForObject($object)
{
@@ -267,11 +316,175 @@ public static function getParametrizedTypesForObject($object)
/**
* @param string $className
- * @param array $typeNames
+ * @param string[] $typeArguments
+ */
+ public static function registerClass($className, array $typeArguments)
+ {
+ $x = array();
+ foreach ($typeArguments as $arg) $x[] = TypeArgument::create($arg);
+
+ echo "LOG: registering class $className\n";
+
+ self::$classes[strtolower($className)] = $x;
+ }
+
+
+
+ /**
+ * Check if object matches generic type (class + type values)
+ * @param object $object
+ * @param string $className
+ * @param TypeValue[] $typeValues
+ * @return bool
+ */
+ public static function checkInstance($object, $className, array $typeValues)
+ {
+ if ( ! $object instanceof $className) return FALSE;
+
+ // TODO
+
+ return TRUE;
+ }
+
+
+
+ /**
+ * Same as #checkInstance, but throws if invalid
+ * @param object $object
+ * @param string $className
+ * @param TypeValue[] $typeValues
+ */
+ public static function ensureInstance($object, $className, array $typeValues)
+ {
+ if ( ! self::checkInstance($object, $className, $typeValues)) throw new Exception("Invalid type");
+ }
+
+
+ /**
+ * For a particular instance of generic class, find the actual class
+ * e.g E needs to be resolved in: return new Collection<E>(...)
+ * @param object $object
+ * @param string $typeArgumentName
+ */
+ public function resolveTypeArgument($object, $typeArgumentName)
+ {
+ if ( ! $typeValues = self::getParametrizedTypesForObject($object)) throw new InvalidArgumentException("Object has no type-values");
+ if ( ! isset($typeValues[$typeArgumentName])) throw new InvalidArgumentException("Object does not have type-value named '$typeArgumentName'");
+
+ return $typeValues[$typeArgumentName]->actualClass;
+ }
+
+
+
+ /***************** utils *****************j*d*/
+
+
+
+ /**
+ * From a generic class and raw type values, get resolved TypeValue's
+ * @param string $className
+ * @param string[]|TypeValue[] $rawTypeValues
+ * @return TypeValue[] typeArgumentName => TypeValue
*/
- public static function registerClass($className, array $typeNames)
+ protected static function resolveTypeValues($className, $rawTypeValues)
+ {
+ $className = strtolower($className);
+
+ if( ! isset(self::$classes[$className])) throw new InvalidArgumentException("Class '$className' is not generic");
+ $typeArguments = self::$classes[$className];
+
+ if(count($typeArguments) !== count($rawTypeValues)) throw new InvalidArgumentException("Generic values do not mach generic arguments");
+
+ $typeValues = array();
+ foreach($typeArguments as $k => $typeArgument) {
+ $val = $rawTypeValues[$k];
+
+ if ( ! $val instanceof TypeValue) { // string -> TypeValue
+ TypeValue::create($typeArgument, $val);
+
+ } else { // validate TypeValue to match
+ // TODO
+ }
+
+ $typeValues[$typeArgument->name] = $val;
+ }
+
+ return $typeValues;
+ }
+
+}
+
+
+
+/**
+ * Type argument for generics
+ *
+ * Examples:
+ * - E
+ * - E extends Entity
+ * - E super RegisteredUserEntity
+ */
+class TypeArgument
+{
+ public $name;
+ public $className;
+ public $extends;
+
+
+
+ public static function create($code)
+ {
+ if (preg_match('~^(\S+)\s+(extends|super)\s+(\S+)$~', $code, $match)) {
+ return new self($match[1], $match[3], $match[2] === 'extends');
+
+ } elseif (preg_match('~^\S+$~', $code)) {
+ return new self($code);
+
+ } else {
+ throw new InvalidArgumentException("Invalid type argument");
+ }
+ }
+
+ protected function __construct($name, $className = NULL, $extends = TRUE)
+ {
+ $this->name = $name;
+ $this->className = $className;
+ $this->extends = $extends;
+ }
+
+ public function matches($actualClassName)
+ {
+ if ( ! $this->className) return TRUE;
+
+ if ($this->extends) { // actualClassName should be instanceof className
+ return Nette\Reflection\ClassType::from($actualClassName)->isSubclassOf($this->className);
+
+ } else { // superclass
+ return Nette\Reflection\ClassType::from($this->className)->isSubclassOf($actualClassName);
+ }
+ }
+
+}
+
+
+/**
+ * Particular instance of TypeArgument
+ */
+class TypeValue extends TypeArgument
+{
+ public $actualClass;
+
+
+
+ public static function create(TypeArgument $arg, $actualClass)
{
- self::$classes[strtolower($className)] = $typeNames;
+ if ( ! $arg->matches($actualClass)) throw new InvalidArgumentException("Incompatible type");
+
+ $ret = new self($arg->name, $arg->className, $arg->extends);
+ $ret->actualClass = $actualClass;
+
+ return $ret;
}
}
+
View
36 examples/03-generics/test.php
@@ -1,4 +1,7 @@
<?php
+define('DO_TRANSLATE', true);
+define('AUTOLOAD_TRANSLATED', true);
+
require_once __DIR__ . '/../bootstrap.php';
@@ -7,9 +10,36 @@
Enhancer\EnhancerStream::$enhancer = new GenericsEnhancer;
// Register autoloader
-$loader = new Enhancer\Loaders\ClassLoader;
-$loader->register(true);
-$loader->add('', __DIR__ . '/classes');
+if (AUTOLOAD_TRANSLATED) { // autoload translated files instead of direct compilation (debugging)
+ $loader = new \Composer\Autoload\ClassLoader();
+ $loader->register(true);
+ $loader->add('', __DIR__ . '/output/classes');
+
+} else {
+ $loader = new Enhancer\Loaders\ClassLoader;
+ $loader->register(true);
+ $loader->add('', __DIR__ . '/classes');
+}
+
+
+/***************** translate *****************j*d*/
+
+if (DO_TRANSLATE) {
+ // translate and save all files (for debugging)
+ foreach(Nette\Utils\Finder::findFiles("*.php")->from(__DIR__ . '/usage', __DIR__ . '/classes') as /** @var SplFileInfo $file */ $file) {
+ $relativePath = substr($file->getRealPath(), strlen(__DIR__) + 1);
+ $outputPath = __DIR__ . '/output/' . $relativePath;
+
+ // echo "$relativePath -> $outputPath\n";
+
+ @mkdir(dirname($outputPath), 0777, true);
+ file_put_contents($outputPath, file_get_contents("enhance://{$file->getRealPath()}"));
+ }
+}
+
+
+/***************** globals *****************j*d*/
+$em = NULL;
/***************** tests *********************/
View
2  examples/03-generics/usage/test01.php
@@ -1,3 +1,3 @@
<?php
-$repository = new ORM\Repository<ORM\Entity\User>();
+$repository = new ORM\Repository<ORM\Entity\User>($em);
View
2  examples/03-generics/usage/test02.php
@@ -2,4 +2,4 @@
use ORM\Entity\User;
-$repository = new ORM\Repository<User>();
+$repository = new ORM\Repository<User>($em);
View
2  examples/03-generics/usage/test04.php
@@ -3,4 +3,4 @@
use ORM\Entity\User;
use ORM\Repository;
-$repository = new Repository<User>();
+$repository = new Repository<User>($em);

No commit comments for this range

Something went wrong with that request. Please try again.