Skip to content

Commit

Permalink
ContainerBuilder: added support for PHP7 type hints
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelkouril authored and dg committed Sep 1, 2015
1 parent 88d5efb commit 34e1806
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 3 deletions.
5 changes: 3 additions & 2 deletions src/DI/ContainerBuilder.php
Expand Up @@ -421,7 +421,7 @@ private function resolveServiceClass($name, $recursive = [])
if ($class = $def->getClass() ?: $class) {
$def->setClass($class);
if (!class_exists($class) && !interface_exists($class)) {
throw new ServiceCreationException("Class or interface $class used in service '$name' not found.");
throw new ServiceCreationException("Type $class used in service '$name' not found or is not class or interface.");
}
self::checkCase($class);

Expand Down Expand Up @@ -638,7 +638,8 @@ private function generateService($name)

$factoryClass->addMethod($def->getImplementType())
->setParameters($this->convertParameters($def->parameters))
->setBody(str_replace('$this', '$this->container', $code));
->setBody(str_replace('$this', '$this->container', $code))
->setReturnType(PHP_VERSION_ID >= 70000 ? $def->getClass() : NULL);

return "return new {$factoryClass->getName()}(\$this);";
}
Expand Down
7 changes: 6 additions & 1 deletion src/DI/PhpReflection.php
Expand Up @@ -59,7 +59,9 @@ public static function getDeclaringClass(\ReflectionProperty $prop)
*/
public static function getParameterType(\ReflectionParameter $param)
{
if ($param->isArray() || $param->isCallable()) {
if (PHP_VERSION_ID >= 70000) {
return $param->hasType() ? (string) $param->getType() : NULL;
} elseif ($param->isArray() || $param->isCallable()) {
return $param->isArray() ? 'array' : 'callable';
} else {
try {
Expand All @@ -79,6 +81,9 @@ public static function getParameterType(\ReflectionParameter $param)
*/
public static function getReturnType(\ReflectionFunctionAbstract $func)
{
if (PHP_VERSION_ID >= 70000 && $func->hasReturnType()) {
return (string) $func->getReturnType();
}
$type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'));
if ($type) {
return $func instanceof \ReflectionMethod
Expand Down
42 changes: 42 additions & 0 deletions tests/DI/Compiler.generatedFactory.returnTypes.phpt
@@ -0,0 +1,42 @@
<?php

/**
* Test: Nette\DI\Compiler: generated services factories from interfaces with return type declarations.
* @phpVersion 7.0
*/

use Nette\DI;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


interface IArticleFactory
{

function create($title): Article;
}

class Article
{
public $title;

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

$compiler = new DI\Compiler;
$container = createContainer($compiler, 'files/compiler.generatedFactory.returnTypes.neon');

Assert::type('IArticleFactory', $container->getService('article'));
$article = $container->getService('article')->create('lorem-ipsum');
Assert::type('Article', $article);
Assert::same('lorem-ipsum', $article->title);

Assert::type('IArticleFactory', $container->getService('article2'));
$article = $container->getService('article2')->create('lorem-ipsum');
Assert::type('Article', $article);
Assert::same('lorem-ipsum', $article->title);
48 changes: 48 additions & 0 deletions tests/DI/Compiler.generatedFactory.scalarParameters.phpt
@@ -0,0 +1,48 @@
<?php

/**
* Test: Nette\DI\Compiler: generated services factories from interfaces with scalar type hints in parameters.
* @phpVersion 7.0
*/

use Nette\DI;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


interface IArticleFactory
{

/** @return Article */
function create(string $title);
}

class Article
{
public $title;

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

$compiler = new DI\Compiler;
$container = createContainer($compiler, 'files/compiler.generatedFactory.scalarParameters.neon');

Assert::type('IArticleFactory', $container->getService('article'));
$article = $container->getService('article')->create('lorem-ipsum');
Assert::type('Article', $article);
Assert::same('lorem-ipsum', $article->title);

Assert::type('IArticleFactory', $container->getService('article2'));
$article = $container->getService('article2')->create('lorem-ipsum');
Assert::type('Article', $article);
Assert::same('lorem-ipsum', $article->title);

Assert::type('IArticleFactory', $container->getService('article3'));
$article = $container->getService('article3')->create('lorem-ipsum');
Assert::type('Article', $article);
Assert::same('lorem-ipsum', $article->title);
53 changes: 53 additions & 0 deletions tests/DI/ContainerBuilder.factory.resolveBuiltinTypes.php5.phpt
@@ -0,0 +1,53 @@
<?php

/**
* Test: Nette\DI\ContainerBuilder and resolving builtin types for generated factories.
*/

namespace A
{

class Factory
{
/** @return array */
function createArray()
{
return [];
}

/** @return callable */
function createCallable()
{
return function () {};
}
}

}

namespace
{
use Nette\DI;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';

Assert::exception(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('factory')
->setClass('A\Factory');
$builder->addDefinition('a')
->setFactory('@factory::createArray');
$container = createContainer($builder);
}, Nette\DI\ServiceCreationException::class, "Type array used in service 'a' not found or is not class or interface.");

Assert::exception(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('factory')
->setClass('A\Factory');
$builder->addDefinition('c')
->setFactory('@factory::createCallable');
$container = createContainer($builder);
}, Nette\DI\ServiceCreationException::class, "Type callable used in service 'c' not found or is not class or interface.");

}
83 changes: 83 additions & 0 deletions tests/DI/ContainerBuilder.factory.resolveBuiltinTypes.php7.phpt
@@ -0,0 +1,83 @@
<?php

/**
* Test: Nette\DI\ContainerBuilder and resolving builtin types for generated factories. Added checks for types added in PHP 7.0.
* @phpVersion 7.0
*/

namespace A
{

class Factory
{
/** @return string */
function createString()
{
return "";
}

/** @return int */
function createInt()
{
return 0;
}

function createBool(): bool
{
return FALSE;
}

function createFloat(): float
{
return 0.0;
}
}

}

namespace
{
use Nette\DI;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


Assert::exception(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('factory')
->setClass('A\Factory');
$builder->addDefinition('s')
->setFactory('@factory::createString');
$container = createContainer($builder);
}, Nette\DI\ServiceCreationException::class, "Type string used in service 's' not found or is not class or interface.");

Assert::exception(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('factory')
->setClass('A\Factory');
$builder->addDefinition('i')
->setFactory('@factory::createInt');
$container = createContainer($builder);
}, Nette\DI\ServiceCreationException::class, "Type int used in service 'i' not found or is not class or interface.");

Assert::exception(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('factory')
->setClass('A\Factory');
$builder->addDefinition('b')
->setFactory('@factory::createBool');
$container = createContainer($builder);
}, Nette\DI\ServiceCreationException::class, "Type bool used in service 'b' not found or is not class or interface.");

Assert::exception(function () {
$builder = new DI\ContainerBuilder;
$builder->addDefinition('factory')
->setClass('A\Factory');
$builder->addDefinition('f')
->setFactory('@factory::createFloat');
$container = createContainer($builder);
}, Nette\DI\ServiceCreationException::class, "Type float used in service 'f' not found or is not class or interface.");

}
53 changes: 53 additions & 0 deletions tests/DI/ContainerBuilder.factory.resolveClass.returnTypes.phpt
@@ -0,0 +1,53 @@
<?php

/**
* Test: Nette\DI\ContainerBuilder and resolving class in generated factories. Return type is located in method signature instead of @return annotation.
* @phpVersion 7.0
*/

namespace A
{
use B\Bar;

class Factory
{
function createBar(): Bar
{
return new Bar();
}
}

}

namespace B
{

class Bar
{

}

}

namespace
{
use Nette\DI;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


$builder = new DI\ContainerBuilder;

$builder->addDefinition('one')
->setClass('A\Factory');

$builder->addDefinition('two')
->setFactory('@one::createBar');


$container = createContainer($builder);

Assert::type('B\Bar', $container->getByType('B\Bar'));
}
11 changes: 11 additions & 0 deletions tests/DI/files/compiler.generatedFactory.returnTypes.neon
@@ -0,0 +1,11 @@
services:

article:
create: Article(%title%)
implement: IArticleFactory
parameters: [title]

article2:
implement: IArticleFactory
arguments: [%title%]
parameters: [title]
14 changes: 14 additions & 0 deletions tests/DI/files/compiler.generatedFactory.scalarParameters.neon
@@ -0,0 +1,14 @@
services:

article:
create: Article(%title%)
implement: IArticleFactory
parameters: [string title]

article2:
implement: IArticleFactory
arguments: [%title%]
parameters: [string title]

article3:
implement: IArticleFactory

0 comments on commit 34e1806

Please sign in to comment.