Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 16 additions & 20 deletions Command/DebugCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,38 +74,34 @@ protected function execute(InputInterface $input, OutputInterface $output)

/** @var FluentResolverInterface $resolver */
$resolver = $this->{$category.'Resolver'};

$solutions = $this->retrieveSolutions($resolver);
$this->renderTable($tableHeaders, $solutions, $io);
$this->renderTable($resolver, $tableHeaders, $io);
}
}

private function renderTable(array $tableHeaders, array $solutions, SymfonyStyle $io)
private function renderTable(FluentResolverInterface $resolver, array $tableHeaders, SymfonyStyle $io)
{
$tableRows = [];
foreach ($solutions as $id => &$options) {
ksort($options['aliases']);
$tableRows[] = [$id, implode("\n", $options['aliases'])];
$solutionIDs = array_keys($resolver->getSolutions());
sort($solutionIDs);
foreach ($solutionIDs as $solutionID) {
$aliases = $resolver->getSolutionAliases($solutionID);
$aliases[] = $solutionID;
$options = $resolver->getSolutionOptions($solutionID);
$tableRows[$options['id']] = [$options['id'], self::serializeAliases($aliases, $options)];
}
ksort($tableRows);
$io->table($tableHeaders, $tableRows);
$io->write("\n\n");
}

private function retrieveSolutions(FluentResolverInterface $resolver)
private static function serializeAliases(array $aliases, array $options)
{
$data = [];
foreach ($resolver->getSolutions() as $alias => $solution) {
$options = $resolver->getSolutionOptions($alias);

$id = $options['id'];
if (!isset($data[$id]['aliases'])) {
$data[$id]['aliases'] = [];
}
$data[$id]['aliases'][] = $options['alias'].(isset($options['method']) ? ' (method: '.$options['method'].')' : '');
}
ksort($data);
ksort($aliases);
$aliases = array_map(function ($alias) use ($options) {
return $alias.(isset($options['method']) ? ' (method: '.$options['method'].')' : '');
}, $aliases);

return $data;
return implode("\n", $aliases);
}

public static function getCategories()
Expand Down
5 changes: 3 additions & 2 deletions Config/Processor/NamedConfigProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ final class NamedConfigProcessor implements ProcessorInterface
public static function process(array $configs)
{
foreach ($configs as $name => &$config) {
$config['config'] = isset($config['config']) && is_array($config['config']) ? $config['config'] : [];
$config['config']['name'] = $name;
if (empty($config['config']['name'])) {
$config['config']['name'] = $name;
}
}

return $configs;
Expand Down
2 changes: 1 addition & 1 deletion Config/TypeDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected function nameSection()
->ifTrue(function ($name) {
return !preg_match('/^[_a-z][_0-9a-z]*$/i', $name);
})
->thenInvalid('Invalid type name "%s". (see https://facebook.github.io/graphql/#Name)')
->thenInvalid('Invalid type name "%s". (see http://facebook.github.io/graphql/October2016/#Name)')
->end();

return $node;
Expand Down
10 changes: 4 additions & 6 deletions DependencyInjection/Compiler/ConfigTypesPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,16 @@ public function process(ContainerBuilder $container)
->compile(TypeGenerator::MODE_MAPPING_ONLY);

foreach ($generatedClasses as $class => $file) {
$aliases = [preg_replace('/Type$/', '', substr(strrchr($class, '\\'), 1))];
$this->setTypeServiceDefinition($container, $class, $aliases);
$alias = preg_replace('/Type$/', '', substr(strrchr($class, '\\'), 1));
$this->setTypeServiceDefinition($container, $class, $alias);
}
}

private function setTypeServiceDefinition(ContainerBuilder $container, $class, array $aliases)
private function setTypeServiceDefinition(ContainerBuilder $container, $class, $alias)
{
$definition = $container->setDefinition($class, new Definition($class));
$definition->setPublic(false);
$definition->setArguments([new Reference(ConfigProcessor::class), new Reference(GlobalVariables::class)]);
foreach ($aliases as $alias) {
$definition->addTag(TypeTaggedServiceMappingPass::TAG_NAME, ['alias' => $alias, 'generated' => true]);
}
$definition->addTag(TypeTaggedServiceMappingPass::TAG_NAME, ['alias' => $alias, 'generated' => true]);
}
}
102 changes: 66 additions & 36 deletions DependencyInjection/Compiler/TaggedServiceMappingPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

abstract class TaggedServiceMappingPass implements CompilerPassInterface
Expand All @@ -18,23 +19,22 @@ private function getTaggedServiceMapping(ContainerBuilder $container, $tagName)

foreach ($taggedServices as $id => $tags) {
$className = $container->findDefinition($id)->getClass();
foreach ($tags as $tag) {
$this->checkRequirements($id, $tag);
$tag = array_merge($tag, ['id' => $id]);
if (!$isType) {
$tag['method'] = isset($tag['method']) ? $tag['method'] : '__invoke';
foreach ($tags as $attributes) {
$this->checkRequirements($id, $attributes);
$attributes = self::resolveAttributes($attributes, $id, !$isType);
$solutionID = $className;

if (!$isType && '__invoke' !== $attributes['method']) {
$solutionID = sprintf('%s::%s', $className, $attributes['method']);
}
if (isset($tag['alias'])) {
$serviceMapping[$tag['alias']] = $tag;

if (!isset($serviceMapping[$solutionID])) {
$serviceMapping[$solutionID] = $attributes;
}

// add FQCN alias
$alias = $className;
if (!$isType && '__invoke' !== $tag['method']) {
$alias .= '::'.$tag['method'];
if (isset($attributes['alias']) && $solutionID !== $attributes['alias']) {
$serviceMapping[$solutionID]['aliases'][] = $attributes['alias'];
}
$tag['alias'] = $alias;
$serviceMapping[$tag['alias']] = $tag;
}
}

Expand All @@ -46,35 +46,19 @@ public function process(ContainerBuilder $container)
$mapping = $this->getTaggedServiceMapping($container, $this->getTagName());
$resolverDefinition = $container->findDefinition($this->getResolverServiceID());

foreach ($mapping as $name => $options) {
$cleanOptions = $options;
$solutionID = $options['id'];
foreach ($mapping as $solutionID => $attributes) {
$attributes['aliases'] = array_unique($attributes['aliases']);
$aliases = $attributes['aliases'];
$serviceID = $attributes['id'];

$solutionDefinition = $container->findDefinition($solutionID);
$solutionDefinition = $container->findDefinition($serviceID);
// make solution service public to improve lazy loading
$solutionDefinition->setPublic(true);

$methods = array_map(
function ($methodCall) {
return $methodCall[0];
},
$solutionDefinition->getMethodCalls()
);
if (
empty($options['generated']) // false is consider as empty
&& is_subclass_of($solutionDefinition->getClass(), ContainerAwareInterface::class)
&& !in_array('setContainer', $methods)
) {
@trigger_error(
'Autowire custom tagged (type, resolver or mutation) services is deprecated as of 0.9 and will be removed in 1.0. Use AutoMapping or set it manually instead.',
E_USER_DEPRECATED
);
$solutionDefinition->addMethodCall('setContainer', [new Reference('service_container')]);
}
$this->autowireSolutionImplementingContainerAwareInterface($solutionDefinition, empty($attributes['generated']));

$resolverDefinition->addMethodCall(
'addSolution',
[$name, [new Reference('service_container'), 'get'], [$solutionID], $cleanOptions]
[$solutionID, [[new Reference('service_container'), 'get'], [$serviceID]], $aliases, $attributes]
);
}
}
Expand All @@ -88,6 +72,52 @@ protected function checkRequirements($id, array $tag)
}
}

/**
* @param array $attributes
* @param string $id
* @param bool $withMethod
*
* @return array
*/
private static function resolveAttributes(array $attributes, $id, $withMethod)
{
$default = ['id' => $id, 'aliases' => []];
if ($withMethod) {
$default['method'] = '__invoke';
}
$attributes = array_replace($default, $attributes);

return $attributes;
}

/**
* @param Definition $solutionDefinition
* @param bool $isGenerated
*/
private function autowireSolutionImplementingContainerAwareInterface(Definition $solutionDefinition, $isGenerated)
{
$methods = array_map(
function ($methodCall) {
return $methodCall[0];
},
$solutionDefinition->getMethodCalls()
);
if (
$isGenerated
&& is_subclass_of($solutionDefinition->getClass(), ContainerAwareInterface::class)
&& !in_array('setContainer', $methods)
) {
@trigger_error(
sprintf(
'Autowire method "%s::setContainer" for custom tagged (type, resolver or mutation) services is deprecated as of 0.9 and will be removed in 1.0.',
ContainerAwareInterface::class
),
E_USER_DEPRECATED
);
$solutionDefinition->addMethodCall('setContainer', [new Reference('service_container')]);
}
}

abstract protected function getTagName();

/**
Expand Down
89 changes: 63 additions & 26 deletions Resolver/AbstractResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,51 @@ abstract class AbstractResolver implements FluentResolverInterface
/** @var array */
private $solutions = [];

private $aliases = [];

/** @var array */
private $solutionOptions = [];

/** @var array */
private $fullyLoadedSolutions = [];

public function addSolution($name, callable $solutionFunc, array $solutionFuncArgs = [], array $options = [])
public function addSolution($id, $solutionOrFactory, array $aliases = [], array $options = [])
{
$this->fullyLoadedSolutions[$name] = false;
$this->solutions[$name] = function () use ($name, $solutionFunc, $solutionFuncArgs) {
$solution = call_user_func_array($solutionFunc, $solutionFuncArgs);
$this->checkSolution($name, $solution);
$this->fullyLoadedSolutions[$id] = false;
$this->addAliases($id, $aliases);

$this->solutions[$id] = function () use ($id, $solutionOrFactory) {
$solution = $solutionOrFactory;
if (self::isSolutionFactory($solutionOrFactory)) {
if (!isset($solutionOrFactory[1])) {
$solutionOrFactory[1] = [];
}
$solution = call_user_func_array(...$solutionOrFactory);
}
$this->checkSolution($id, $solution);

return $solution;
};
$this->solutionOptions[$name] = $options;
$this->solutionOptions[$id] = $options;

return $this;
}

public function hasSolution($name)
public function hasSolution($id)
{
return isset($this->solutions[$name]);
$id = $this->resolveAlias($id);

return isset($this->solutions[$id]);
}

/**
* @param $name
* @param $id
*
* @return mixed
*/
public function getSolution($name)
public function getSolution($id)
{
return $this->loadSolution($name);
return $this->loadSolution($id);
}

/**
Expand All @@ -50,38 +62,63 @@ public function getSolutions()
return $this->loadSolutions();
}

public function getSolutionAliases($id)
{
return array_keys($this->aliases, $id);
}

/**
* @param $name
* @param $id
*
* @return mixed
*/
public function getSolutionOptions($name)
public function getSolutionOptions($id)
{
return isset($this->solutionOptions[$name]) ? $this->solutionOptions[$name] : [];
$id = $this->resolveAlias($id);

return isset($this->solutionOptions[$id]) ? $this->solutionOptions[$id] : [];
}

/**
* @param string $name
* @param string $id
*
* @return mixed
*/
private function loadSolution($name)
private function loadSolution($id)
{
if (!$this->hasSolution($name)) {
$id = $this->resolveAlias($id);
if (!$this->hasSolution($id)) {
return null;
}

if ($this->fullyLoadedSolutions[$name]) {
return $this->solutions[$name];
if ($this->fullyLoadedSolutions[$id]) {
return $this->solutions[$id];
} else {
$loader = $this->solutions[$name];
$this->solutions[$name] = $loader();
$this->fullyLoadedSolutions[$name] = true;
$loader = $this->solutions[$id];
$this->solutions[$id] = $loader();
$this->fullyLoadedSolutions[$id] = true;

return $this->solutions[$id];
}
}

return $this->solutions[$name];
private function addAliases($id, $aliases)
{
foreach ($aliases as $alias) {
$this->aliases[$alias] = $id;
}
}

private static function isSolutionFactory($solutionOrFactory)
{
return is_array($solutionOrFactory) && isset($solutionOrFactory[0]) && is_callable($solutionOrFactory[0]);
}

private function resolveAlias($alias)
{
return isset($this->aliases[$alias]) ? $this->aliases[$alias] : $alias;
}

/**
* @return mixed[]
*/
Expand All @@ -106,11 +143,11 @@ protected function supportsSolution($solution)
return null === $supportedClass || $solution instanceof $supportedClass;
}

protected function checkSolution($name, $solution)
protected function checkSolution($id, $solution)
{
if (!$this->supportsSolution($solution)) {
throw new UnsupportedResolverException(
sprintf('Resolver "%s" must be "%s" "%s" given.', $name, $this->supportedSolutionClass(), get_class($solution))
sprintf('Resolver "%s" must be "%s" "%s" given.', $id, $this->supportedSolutionClass(), get_class($solution))
);
}
}
Expand All @@ -122,6 +159,6 @@ protected function checkSolution($name, $solution)
*/
protected function supportedSolutionClass()
{
return;
return null;
}
}
Loading