Skip to content

feat: replace thecodingmachine/class-explorer with kcs/class-finder #664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 20, 2024
Merged
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
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -25,8 +25,8 @@
"symfony/cache": "^4.3 || ^5 || ^6 || ^7",
"symfony/expression-language": "^4 || ^5 || ^6 || ^7",
"thecodingmachine/cache-utils": "^1",
"thecodingmachine/class-explorer": "^1.1.0",
"webonyx/graphql-php": "^v15.0"
"webonyx/graphql-php": "^v15.0",
"kcs/class-finder": "^0.4.0"
},
"require-dev": {
"beberlei/porpaginas": "^1.2 || ^2.0",
31 changes: 13 additions & 18 deletions src/GlobControllerQueryProvider.php
Original file line number Diff line number Diff line change
@@ -6,14 +6,14 @@

use GraphQL\Type\Definition\FieldDefinition;
use InvalidArgumentException;
use Mouf\Composer\ClassNameMapper;
use Kcs\ClassFinder\Finder\ComposerFinder;
use Kcs\ClassFinder\Finder\FinderInterface;
use Psr\Container\ContainerInterface;
use Psr\SimpleCache\CacheInterface;
use ReflectionClass;
use ReflectionMethod;
use Symfony\Component\Cache\Adapter\Psr16Adapter;
use Symfony\Contracts\Cache\CacheInterface as CacheContractInterface;
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
use TheCodingMachine\GraphQLite\Annotations\Mutation;
use TheCodingMachine\GraphQLite\Annotations\Query;
use TheCodingMachine\GraphQLite\Annotations\Subscription;
@@ -33,27 +33,25 @@ final class GlobControllerQueryProvider implements QueryProviderInterface
{
/** @var array<int,string>|null */
private array|null $instancesList = null;
private ClassNameMapper $classNameMapper;
private FinderInterface $finder;
private AggregateControllerQueryProvider|null $aggregateControllerQueryProvider = null;
private CacheContractInterface $cacheContract;

/**
* @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
* @param ContainerInterface $container The container we will fetch controllers from.
* @param bool $recursive Whether subnamespaces of $namespace must be analyzed.
*/
public function __construct(
private string $namespace,
private FieldsBuilder $fieldsBuilder,
private ContainerInterface $container,
private AnnotationReader $annotationReader,
private CacheInterface $cache,
ClassNameMapper|null $classNameMapper = null,
private int|null $cacheTtl = null,
private bool $recursive = true,
private readonly string $namespace,
private readonly FieldsBuilder $fieldsBuilder,
private readonly ContainerInterface $container,
private readonly AnnotationReader $annotationReader,
private readonly CacheInterface $cache,
FinderInterface|null $finder = null,
int|null $cacheTtl = null,
)
{
$this->classNameMapper = $classNameMapper ?? ClassNameMapper::createFromComposerFile(null, null, true);
$this->finder = $finder ?? new ComposerFinder();
$this->cacheContract = new Psr16Adapter(
$this->cache,
str_replace(['\\', '{', '}', '(', ')', '/', '@', ':'], '_', $namespace),
@@ -96,15 +94,12 @@ private function getInstancesList(): array
/** @return array<int,string> */
private function buildInstancesList(): array
{
$explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->cacheTtl, $this->classNameMapper, $this->recursive);
$classes = $explorer->getClasses();
$instances = [];
foreach ($classes as $className) {
foreach ((clone $this->finder)->inNamespace($this->namespace) as $className => $refClass) {
if (! class_exists($className) && ! interface_exists($className)) {
continue;
}
$refClass = new ReflectionClass($className);
if (! $refClass->isInstantiable()) {
if (! $refClass instanceof ReflectionClass || ! $refClass->isInstantiable()) {
continue;
}
if (! $this->hasOperations($refClass)) {
12 changes: 6 additions & 6 deletions src/SchemaFactory.php
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
use Doctrine\Common\Annotations\PsrCachedReader;
use Doctrine\Common\Annotations\Reader;
use GraphQL\Type\SchemaConfig;
use Mouf\Composer\ClassNameMapper;
use Kcs\ClassFinder\Finder\FinderInterface;
use MyCLabs\Enum\Enum;
use PackageVersions\Versions;
use Psr\Cache\CacheItemPoolInterface;
@@ -109,7 +109,7 @@ class SchemaFactory

private NamingStrategyInterface|null $namingStrategy = null;

private ClassNameMapper|null $classNameMapper = null;
private FinderInterface|null $finder = null;

private SchemaConfig|null $schemaConfig = null;

@@ -262,9 +262,9 @@ public function setSchemaConfig(SchemaConfig $schemaConfig): self
return $this;
}

public function setClassNameMapper(ClassNameMapper $classNameMapper): self
public function setFinder(FinderInterface $finder): self
{
$this->classNameMapper = $classNameMapper;
$this->finder = $finder;

return $this;
}
@@ -344,7 +344,7 @@ public function createSchema(): Schema
$namingStrategy = $this->namingStrategy ?: new NamingStrategy();
$typeRegistry = new TypeRegistry();

$namespaceFactory = new NamespaceFactory($namespacedCache, $this->classNameMapper, $this->globTTL);
$namespaceFactory = new NamespaceFactory($namespacedCache, $this->finder, $this->globTTL);
$nsList = array_map(
static fn (string $namespace) => $namespaceFactory->createNamespace($namespace),
$this->typeNamespaces,
@@ -493,7 +493,7 @@ public function createSchema(): Schema
$this->container,
$annotationReader,
$namespacedCache,
$this->classNameMapper,
$this->finder,
$this->globTTL,
);
}
70 changes: 46 additions & 24 deletions src/Utils/Namespaces/NS.php
Original file line number Diff line number Diff line change
@@ -4,13 +4,19 @@

namespace TheCodingMachine\GraphQLite\Utils\Namespaces;

use Mouf\Composer\ClassNameMapper;
use Exception;
use Kcs\ClassFinder\Finder\FinderInterface;
use Psr\SimpleCache\CacheException;
use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException;
use ReflectionClass;
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
use ReflectionException;

use function array_keys;
use function class_exists;
use function interface_exists;
use function preg_replace;
use function trait_exists;

/**
* The NS class represents a PHP Namespace and provides utility methods to explore those classes.
@@ -24,18 +30,18 @@ final class NS
* Only instantiable classes are returned.
* Key: fully qualified class name
*
* @var array<string,ReflectionClass<object>>
* @var array<class-string,ReflectionClass<object>>
*/
private array|null $classes = null;

/** @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation) */
public function __construct(
private readonly string $namespace,
private readonly CacheInterface $cache,
private readonly ClassNameMapper $classNameMapper,
private readonly FinderInterface $finder,
private readonly int|null $globTTL,
private readonly bool $recursive,
) {
)
{
}

/**
@@ -47,31 +53,47 @@ public function __construct(
public function getClassList(): array
{
if ($this->classes === null) {
$this->classes = [];
$explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->globTTL, $this->classNameMapper, $this->recursive);
/** @var array<class-string, string> $classes Override class-explorer lib */
$classes = $explorer->getClassMap();
foreach ($classes as $className => $phpFile) {
if (! class_exists($className, false) && ! interface_exists($className, false)) {
// Let's try to load the file if it was not imported yet.
// We are importing the file manually to avoid triggering the autoloader.
// The autoloader might trigger errors if the file does not respect PSR-4 or if the
// Symfony DebugAutoLoader is installed. (see https://github.com/thecodingmachine/graphqlite/issues/216)
require_once $phpFile;
// Does it exists now?
// @phpstan-ignore-next-line
if (! class_exists($className, false) && ! interface_exists($className, false)) {
continue;
$cacheKey = 'GraphQLite_NS_' . preg_replace('/[\/{}()\\\\@:]/', '', $this->namespace);
try {
$classes = $this->cache->get($cacheKey);
if ($classes !== null) {
foreach ($classes as $class) {
if (
! class_exists($class, false) &&
! interface_exists($class, false) &&
! trait_exists($class, false)
) {
// assume the cache is invalid
throw new class extends Exception implements CacheException {
};
}

$this->classes[$class] = new ReflectionClass($class);
}
}
} catch (CacheException | InvalidArgumentException | ReflectionException) {
$this->classes = null;
}

$refClass = new ReflectionClass($className);
if ($this->classes === null) {
$this->classes = [];
/** @var class-string $className */
/** @var ReflectionClass<object> $reflector */
foreach ($this->finder->inNamespace($this->namespace) as $className => $reflector) {
if (! ($reflector instanceof ReflectionClass)) {
continue;
}

$this->classes[$className] = $refClass;
$this->classes[$className] = $reflector;
}
try {
$this->cache->set($cacheKey, array_keys($this->classes), $this->globTTL);
} catch (InvalidArgumentException) {
// @ignoreException
}
}
}

// @phpstan-ignore-next-line - Not sure why we cannot annotate the $classes above
return $this->classes;
}

13 changes: 7 additions & 6 deletions src/Utils/Namespaces/NamespaceFactory.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,8 @@

namespace TheCodingMachine\GraphQLite\Utils\Namespaces;

use Mouf\Composer\ClassNameMapper;
use Kcs\ClassFinder\Finder\ComposerFinder;
use Kcs\ClassFinder\Finder\FinderInterface;
use Psr\SimpleCache\CacheInterface;

/**
@@ -14,16 +15,16 @@
*/
final class NamespaceFactory
{
private ClassNameMapper $classNameMapper;
private FinderInterface $finder;

public function __construct(private readonly CacheInterface $cache, ClassNameMapper|null $classNameMapper = null, private int|null $globTTL = 2)
public function __construct(private readonly CacheInterface $cache, FinderInterface|null $finder = null, private int|null $globTTL = 2)
{
$this->classNameMapper = $classNameMapper ?? ClassNameMapper::createFromComposerFile(null, null, true);
$this->finder = $finder ?? new ComposerFinder();
}

/** @param string $namespace A PHP namespace */
public function createNamespace(string $namespace, bool $recursive = true): NS
public function createNamespace(string $namespace): NS
{
return new NS($namespace, $this->cache, $this->classNameMapper, $this->globTTL, $recursive);
return new NS($namespace, $this->cache, clone $this->finder, $this->globTTL);
}
}
8 changes: 8 additions & 0 deletions tests/Fixtures/BadNamespace/BadlyNamespacedClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace TheCodingMachine\GraphQLite\Fixtures\BadNamespace\None;

class BadlyNamespacedClass
{

}
5 changes: 5 additions & 0 deletions tests/Fixtures/BadNamespace/ClassWithoutNamespace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
class ClassWithoutNamespace
{

}
8 changes: 8 additions & 0 deletions tests/Fixtures/Types/EnumType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace TheCodingMachine\GraphQLite\Fixtures\Types;

enum EnumType
{

}
23 changes: 13 additions & 10 deletions tests/GlobControllerQueryProviderTest.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite;

use Kcs\ClassFinder\Finder\ComposerFinder;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Psr16Cache;
use TheCodingMachine\GraphQLite\Fixtures\TestController;
@@ -13,37 +17,36 @@ public function testGlob(): void
{
$controller = new TestController();

$container = new class([ TestController::class => $controller ]) implements ContainerInterface {
/**
* @var array
*/
$container = new class ([TestController::class => $controller]) implements ContainerInterface {
/** @var array */
private $controllers;

public function __construct(array $controllers)
{
$this->controllers = $controllers;
}

public function get($id):mixed
public function get($id): mixed
{
return $this->controllers[$id];
}

public function has($id):bool
public function has($id): bool
{
return isset($this->controllers[$id]);
}
};

$finder = new ComposerFinder();
$finder->filter(static fn (ReflectionClass $class) => $class->getNamespaceName() === 'TheCodingMachine\\GraphQLite\\Fixtures'); // Fix for recursive:false
$globControllerQueryProvider = new GlobControllerQueryProvider(
'TheCodingMachine\\GraphQLite\\Fixtures',
$this->getFieldsBuilder(),
$container,
$this->getAnnotationReader(),
new Psr16Cache(new NullAdapter),
null,
false,
false,
new Psr16Cache(new NullAdapter()),
$finder,
0,
);

$queries = $globControllerQueryProvider->getQueries();
Loading