graphqlite-laravel icon indicating copy to clipboard operation
graphqlite-laravel copied to clipboard

Can we get Lumen Support for this package?

Open rajeev-k-tomy opened this issue 4 years ago • 1 comments

Hi,

Thank you for the awesome work!

I was trying this package with Lumen and found that the package won't work with the framework.

I found the following issues mainly:

  1. config_path is not available in Lumen. Hence the the \TheCodingMachine\GraphQLite\Laravel\Providers\GraphQLiteServiceProvider::boot() method will fail.
  2. There is no web middleware available in the Lumen. Since the routes definition in the package depends on the web middleware, it will gracefully fail at the moment.
  3. Dependency on Illuminate\Contracts\Foundation\Application will again become a roadblock in the \TheCodingMachine\GraphQLite\Laravel\Providers\GraphQLiteServiceProvider::register() method. This is because Lumen uses different Application instance (Laravel\Lumen\Application).

I would appreciate any kind of help on this. Meanwhile, I will provide the nasty workaround for all these problems I faced below.

PS: I am using Lumen version ^8.0.

rajeev-k-tomy avatar Nov 29 '20 10:11 rajeev-k-tomy

Hi a work around is given below:

  1. Updatebootstrap/app.php with below content.
$app->configure('graphqlite');

Then create config/graphqlite.php and add below content as per the doc suggests

use GraphQL\Error\Debug;

return [
    'controllers' => 'App\\Http\\Controller',
    'types' => 'App\\',
    'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,
    'uri' => env('GRAPHQLITE_URI', '/graphql'),
    'middleware' => ['web'],
    'guard' => ['web'],
];
  1. Update bootstrap/app.php with web middleware definition. Here you should ideally add csrf token verification if I understand the doc correctly. For the time being, I am just providing it with the ExampleMiddleware that comes along with Lumen framework.
$app->routeMiddleware([
   'web' => App\Http\Middleware\ExampleMiddleware::class,
]);
  1. Finally you need to create a custom service Provider that wraps the \TheCodingMachine\GraphQLite\Laravel\Providers\GraphQLiteServiceProvider and modify the boot() and register() method in order to avoid the issues I listed out in the above question.

So File: bootstrap/app.php

$app->register(App\Providers\GraphQLiteProvider::class);

File: app/Providers/GraphQLiteProvider.php

<?php
namespace App\Providers;

use GraphQL\Error\Debug;
use GraphQL\Server\ServerConfig;
use GraphQL\Server\StandardServer;
use GraphQL\Type\Schema as WebonyxSchema;
use Illuminate\Contracts\Auth\Factory as AuthFactory;
use Illuminate\Contracts\Events\Dispatcher;
use Laminas\Diactoros\ResponseFactory;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\StreamFactory;
use Laminas\Diactoros\UploadedFileFactory;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Cache\Psr16Cache;
use TheCodingMachine\GraphQLite\Context\Context;
use TheCodingMachine\GraphQLite\Exceptions\WebonyxErrorHandler;
use TheCodingMachine\GraphQLite\Laravel\Controllers\GraphQLiteController;
use TheCodingMachine\GraphQLite\Laravel\Listeners\CachePurger;
use TheCodingMachine\GraphQLite\Laravel\Mappers\PaginatorTypeMapperFactory;
use TheCodingMachine\GraphQLite\Laravel\Mappers\Parameters\ValidateFieldMiddleware;
use TheCodingMachine\GraphQLite\Laravel\SanePsr11ContainerAdapter;
use TheCodingMachine\GraphQLite\Laravel\Security\AuthenticationService;
use TheCodingMachine\GraphQLite\Laravel\Security\AuthorizationService;
use TheCodingMachine\GraphQLite\Schema;
use TheCodingMachine\GraphQLite\SchemaFactory;
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;

class GraphQLiteProvider
    extends \TheCodingMachine\GraphQLite\Laravel\Providers\GraphQLiteServiceProvider
{
    public function boot(Dispatcher $events)
    {
        $this->loadRoutesFrom($this->getParentClassDirectory() . '/../routes/routes.php');
        $events->listen('cache:clearing', CachePurger::class);
    }

    public function register()
    {
        $this->app->bind(WebonyxSchema::class, Schema::class);

        if (!$this->app->has(ServerRequestFactoryInterface::class)) {
            $this->app->bind(ServerRequestFactoryInterface::class, ServerRequestFactory::class);
        }
        if (!$this->app->has(StreamFactoryInterface::class)) {
            $this->app->bind(StreamFactoryInterface::class, StreamFactory::class);
        }
        if (!$this->app->has(UploadedFileFactoryInterface::class)) {
            $this->app->bind(UploadedFileFactoryInterface::class, UploadedFileFactory::class);
        }
        if (!$this->app->has(ResponseFactoryInterface::class)) {
            $this->app->bind(ResponseFactoryInterface::class, ResponseFactory::class);
        }

        $this->app->bind(HttpMessageFactoryInterface::class, PsrHttpFactory::class);

        $this->app->singleton(GraphQLiteController::class, function ($app) {
            $debug = config('graphqlite.debug', Debug::RETHROW_UNSAFE_EXCEPTIONS);

            return new GraphQLiteController($app[StandardServer::class], $app[HttpMessageFactoryInterface::class], $debug);
        });

        $this->app->singleton(StandardServer::class, static function ($app) {
            return new StandardServer($app[ServerConfig::class]);
        });

        $this->app->singleton(ServerConfig::class, static function ($app) {
            $serverConfig = new ServerConfig();
            $serverConfig->setSchema($app[Schema::class]);
            $serverConfig->setErrorFormatter([WebonyxErrorHandler::class, 'errorFormatter']);
            $serverConfig->setErrorsHandler([WebonyxErrorHandler::class, 'errorHandler']);
            $serverConfig->setContext(new Context());
            return $serverConfig;
        });

        $this->app->singleton('graphqliteCache', static function () {
            if (extension_loaded('apcu') && ini_get('apc.enabled')) {
                return new Psr16Cache(new ApcuAdapter());
            } else {
                return new Psr16Cache(new PhpFilesAdapter());
            }
        });

        $this->app->singleton(CachePurger::class, static function ($app) {
            return new CachePurger($app['graphqliteCache']);
        });

        $this->app->singleton(AuthenticationService::class, function($app) {
            $guard = config('graphqlite.guard', $this->app['config']['auth.defaults.guard']);
            if (!is_array($guard)) {
                $guard = [$guard];
            }
            return new AuthenticationService($app[AuthFactory::class], $guard);
        });

        $this->app->bind(AuthenticationServiceInterface::class, AuthenticationService::class);

        $this->app->singleton(SchemaFactory::class, function ($app) {
            $service = new SchemaFactory($app->make('graphqliteCache'), new SanePsr11ContainerAdapter($app));
            $service->setAuthenticationService($app[AuthenticationService::class]);
            $service->setAuthorizationService($app[AuthorizationService::class]);
            $service->addParameterMiddleware($app[ValidateFieldMiddleware::class]);

            $service->addTypeMapperFactory($app[PaginatorTypeMapperFactory::class]);

            $controllers = config('graphqlite.controllers', 'App\\Http\\Controllers');
            if (!is_iterable($controllers)) {
                $controllers = [ $controllers ];
            }
            $types = config('graphqlite.types', 'App\\');
            if (!is_iterable($types)) {
                $types = [ $types ];
            }
            foreach ($controllers as $namespace) {
                $service->addControllerNamespace($namespace);
            }
            foreach ($types as $namespace) {
                $service->addTypeNamespace($namespace);
            }

            if ($this->app->environment('production')) {
                $service->prodMode();
            }

            return $service;
        });

        $this->app->singleton(Schema::class, function ($app) {
            /** @var SchemaFactory $schemaFactory */
            $schemaFactory = $app->make(SchemaFactory::class);

            return $schemaFactory->createSchema();
        });
    }


    private function getParentClassDirectory(): string
    {
        $reflection = new \ReflectionObject($this);

        return pathinfo($reflection->getParentClass()->getFileName(), PATHINFO_DIRNAME);
    }
}

With these changes, you are good to go.

Cheers, Rajeev

rajeev-k-tomy avatar Nov 29 '20 10:11 rajeev-k-tomy