nyholm/sunflower

The best Kernel for simple applications.

Fund package maintenance!
Nyholm

0.2.1 2021-09-05 02:36 UTC

This package is auto-updated.

Last update: 2024-06-14 14:09:39 UTC


README

Sunflower is a super small application kernel that is used to build a dependency injection container. This kernel is useful for microservices and applications that dont use HTTP. Say; reading from a queue or application invoked by AWS Lambda.

With this kernel you can use normal Symfony service definition with auto wiring and all. It even supports Symfony bundles!

The main difference from using symfony/http-kernel and Symfony FrameworkBundle is that Sunflower does not use symfony/event-dispatcher, symfony/console, symfony/security, symfony/cache and symfony/router.

Performance

Below is a table of requests per second using a "hello world" application with different frameworks. The exact numbers are not relevant, they depend on the machine the tests was running on. But one should consider how the numbers change between frameworks since all test ran on the same machine.

Framework Req/s
Sunflower 2.548
Symfony 6.0 1.819
Symfony 5.4 1.804
Slim 4 1.380
Mezzio 3 985
Laravel 8 421

Using a "hello world" comparison has some drawbacks. It does show performance for small applications with only a few hundreds lines of code, but it does not tell how large applications preform. It also does not give you any indication how fast you can write and maintain your application.

The table above is interesting if you are planning to build a small microservice that are similar to "hello world". Using the Sunflower Kernel is also very interesting if you are familiar with Symfony dependency injections, config and third party bundles.

Install

composer require nyholm/sunflower

Use

// src/Kernel.php
namespace App;

use Nyholm\SunflowerKernel;

class Kernel extends SunflowerKernel
{
   /**
    * Optionally override the configureContainer()
    */
   protected function configureContainer(ContainerConfigurator $container): void
    {
        $container->import('../config/{packages}/*.yaml');
        $container->import('../config/{packages}/'.$this->environment.'/*.yaml');
        $container->import('../config/{packages}/'.$this->environment.'/*.php');

        if (\is_file(\dirname(__DIR__).'/config/services.yaml')) {
            $container->import('../config/services.yaml');
            $container->import('../config/{services}_'.$this->environment.'.yaml');
        } else {
            $container->import('../config/{services}.php');
        }
    }
}
use App\Kernel;
use App\Service\MyService;

require_once dirname(__DIR__).'/vendor/autoload.php';

$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
$kernel->getContainer()->get(MyService::class)->run();

Use with HTTP

A short example using HTTP and a simple switch-router. This example is using runtime/psr-nyholm.

// public/index.php

use Nyholm\Psr7;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    $kernel = new \App\Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
    $container = $kernel->getContainer();

    // This is an example router
    $urlPath = $context['REQUEST_URI'];
    switch ($urlPath) {
        case '/':
        case '':
            // This is an RequestHandlerInterface
            return $container->get(\App\Controller\Startpage::class);
        case '/foobar':
            return $container->get(\App\Controller\Foobar::class);
        default:
            return new Psr7\Response(404, [], 'The route does not exist');
    }
};
# config/services.yaml

services:
    _defaults:
        autowire: true
        autoconfigure: true

    _instanceof:
        Psr\Http\Server\RequestHandlerInterface:
            public: true

    App\:
        resource: '../src/'
        exclude:
            - '../src/Kernel.php'

Use with Bref

To create apps that works with Bref you will need the runtime/bref package. Create microservices, SQS readers or react to S3 events etc.

// src/Kernel.php

namespace App;

use Nyholm\SunflowerKernel;

class Kernel extends SunflowerKernel
{
    public function isLambda(): bool
    {
        return false !== \getenv('LAMBDA_TASK_ROOT');
    }

    public function getCacheDir(): string
    {
        if ($this->isLambda()) {
            return '/tmp/cache/'.$this->environment;
        }

        return parent::getCacheDir();
    }

    public function getLogDir(): string
    {
        if ($this->isLambda()) {
            return '/tmp/log/';
        }

        return parent::getLogDir();
    }

    public function getProjectDir(): string
    {
        return \dirname(__DIR__);
    }
}
// bin/container.php

use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

    return $kernel->getContainer();
};
# config/services.yaml

services:
    _defaults:
        autowire: true
        autoconfigure: true

    _instanceof:
        Bref\Event\Handler:
            public: true

    App\:
        resource: '../src/'
        exclude:
            - '../src/Kernel.php'
 # serverless.yml

  functions:
      app:
          handler: bin/container.php:App\Service\MyHandler

Use with CLI

// bin/console
#!/usr/bin/env php
<?php

use App\Kernel;
use Symfony\Component\Console\Application;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    $kernel = new Kernel($context['APP_ENV'], (bool)$context['APP_DEBUG']);
    $container = $kernel->getContainer();

    $app = new Application();
    // Register all your commands here
    $app->add($container->get(\App\Command\HelloCommand::class));

    return $app;
};
declare(strict_types=1);

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:debug:hello')]
class HelloCommand extends Command
{
    protected function configure()
    {
        $this->setDescription('Test print hello.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('Hello');

        return Command::SUCCESS;
    }
}
# config/services.yaml

services:
    _defaults:
        autowire: true
        autoconfigure: true

    _instanceof:
        Symfony\Component\Console\Command\Command:
            public: true

    App\:
        resource: '../src/'
        exclude:
            - '../src/Kernel.php'

History

The Sunflower project was open sourced in 2021. The very first version of the project was created back in 2015. A few private applications was created around the concept of using Symfony's Dependency Injection component but not use the FrameworkBundle or HttpKernel.

The first public version of the project was SuperSlim. That version was a opinionated framework to show what the FrameworkBundle actually did for you behind the scenes. With some more private iterations and many more applications created, we finally removed all unnecessary things and ended up with just the one Kernel.