Skip to content

Commit

Permalink
Merge pull request #396 from jolicode/kernel
Browse files Browse the repository at this point in the history
Refactor the whole application kernel
  • Loading branch information
lyrixx authored Apr 9, 2024
2 parents e5a7acc + ee0783b commit 7da8e06
Show file tree
Hide file tree
Showing 32 changed files with 488 additions and 320 deletions.
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

* Add support for SSH connection without specifying a user
* Marked SSH features as stable
* Deprecate `Castor\GlobalHelper` class. There are no replacements. Use raw
functions instead.
* Import and load task from remote import automatically
* Fix multiple remote imports of the same package with default version
* Do not load task from `vendor` directory
* Add `context()` function in expression language to enable a task
* Fix import of local tasks when using remote import

* Deprecate `Castor\GlobalHelper` class. There are no replacements. Use raw
functions instead
* Deprecate `AfterApplicationInitializationEvent` event. Use
`FunctionsResolvedEvent` instead.

## 0.15.0 (2024-04-03)

Expand Down
9 changes: 9 additions & 0 deletions bin/generate-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,19 @@
$_SERVER['ENDPOINT'] ??= 'http://127.0.0.1:9955';
WebServerHelper::start();

displayTitle('Cleaning');

$fs = new Filesystem();
$fs->remove(PlatformHelper::getCacheDirectory());
$fs->remove(__DIR__ . '/../tests/Generated');
$fs->mkdir(__DIR__ . '/../tests/Generated');
$fs->remove((new Finder())
->in(__DIR__ . '/../tests/fixtures')
->in(__DIR__ . '/../tests/fixtures')
->path('composer.installed')
->ignoreDotFiles(false)
);
echo "\nDone.\n";

displayTitle('Retrieving example tasks');

Expand Down
15 changes: 9 additions & 6 deletions doc/going-further/extending-castor/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ You can register a listener inside your Castor project by using the
the targeted event and the priority of this listener.

```php
#[AsListener(event: AfterApplicationInitializationEvent::class)]
#[AsListener(event: AfterExecuteTaskEvent::class, priority: 1)]
function my_event_listener(AfterApplicationInitializationEvent|AfterExecuteTaskEvent $event): void
use Castor\Event\AfterExecuteTaskEvent;
use Castor\Event\FunctionsResolvedEvent;

#[AsListener(event: AfterExecuteTaskEvent::class)]
#[AsListener(event: FunctionsResolvedEvent::class, priority: 1)]
function my_event_listener(AfterExecuteTaskEvent|FunctionsResolvedEvent $event): void
{
// Custom logic to handle the events
}
Expand All @@ -30,9 +33,9 @@ function my_event_listener(AfterApplicationInitializationEvent|AfterExecuteTaskE

Here is the built-in events triggered by Castor:

* `Castor\Event\AfterApplicationInitializationEvent`: This event is triggered
after the application has been initialized. It provides access to the
`Application` instance and an array of `TaskDescriptor` objects;
* `Castor\Event\FunctionsResolvedEvent`: This event is triggered after the
functions has been resolved. It provides access to an array of of
`TaskDescriptor` and `SymfonyTaskDescriptor` objects;

* `Castor\Event\BeforeExecuteTaskEvent`: This event is triggered before
executing a task. It provides access to the `TaskCommand` instance;
Expand Down
111 changes: 8 additions & 103 deletions src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,15 @@

namespace Castor\Console;

use Castor\Console\Command\SymfonyTaskCommand;
use Castor\Console\Output\VerbosityLevel;
use Castor\Container;
use Castor\Context;
use Castor\ContextRegistry;
use Castor\Event\AfterApplicationInitializationEvent;
use Castor\Event\BeforeApplicationBootEvent;
use Castor\Event\BeforeApplicationInitializationEvent;
use Castor\Factory\TaskCommandFactory;
use Castor\Function\FunctionLoader;
use Castor\Helper\PlatformHelper;
use Castor\Import\Importer;
use Castor\Import\Kernel;
use Castor\Kernel;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/** @internal */
class Application extends SymfonyApplication
Expand All @@ -34,15 +21,8 @@ class Application extends SymfonyApplication
private Command $command;

public function __construct(
private readonly string $rootDir,
private readonly ContainerBuilder $containerBuilder,
private readonly EventDispatcherInterface $eventDispatcher,
#[Autowire(lazy: true)]
private readonly Importer $importer,
private readonly FunctionLoader $functionLoader,
private readonly Kernel $kernel,
private readonly ContextRegistry $contextRegistry,
private readonly TaskCommandFactory $taskCommandFactory,
) {
parent::__construct(static::NAME, static::VERSION);
}
Expand All @@ -55,8 +35,6 @@ public function getCommand(bool $allowNull = false): ?Command
return $this->command ?? ($allowNull ? null : throw new \LogicException('Command not available yet.'));
}

// We do all the logic as late as possible to ensure the exception handler
// is registered
public function doRun(InputInterface $input, OutputInterface $output): int
{
$this->containerBuilder->set(InputInterface::class, $input);
Expand All @@ -65,39 +43,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int
// @phpstan-ignore-next-line
Container::set($this->containerBuilder->get(Container::class));

$this->eventDispatcher->dispatch(new BeforeApplicationBootEvent($this));

$currentFunctions = get_defined_functions()['user'];
$currentClasses = get_declared_classes();

$functionsRootDir = $this->rootDir;
if (class_exists(\RepackedApplication::class)) {
$functionsRootDir = \RepackedApplication::ROOT_DIR;
}
$this->importer->require($functionsRootDir);

$this->eventDispatcher->dispatch(new BeforeApplicationInitializationEvent($this));

$taskDescriptorCollection = $this->functionLoader->load($currentFunctions, $currentClasses);
$taskDescriptorCollection = $taskDescriptorCollection->merge($this->kernel->mount());

$this->initializeApplication($input);

// Must be done after the initializeApplication() call, to ensure all
// contexts have been created; but before the adding of task, because we
// may want to seek in the context to know if the command is enabled
$this->configureContext($input, $output);

$event = new AfterApplicationInitializationEvent($this, $taskDescriptorCollection);
$this->eventDispatcher->dispatch($event);
$taskDescriptorCollection = $event->taskDescriptorCollection;

foreach ($taskDescriptorCollection->taskDescriptors as $taskDescriptor) {
$this->add($this->taskCommandFactory->createTask($taskDescriptor));
}
foreach ($taskDescriptorCollection->symfonyTaskDescriptors as $symfonyTaskDescriptor) {
$this->add(SymfonyTaskCommand::createFromDescriptor($symfonyTaskDescriptor));
}
$this->kernel->boot($input, $output);

return parent::doRun($input, $output);
}
Expand All @@ -109,26 +55,11 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
return parent::doRunCommand($command, $input, $output);
}

private function initializeApplication(InputInterface $input): void
protected function getDefaultInputDefinition(): InputDefinition
{
$this->contextRegistry->setDefaultIfEmpty();

$contextNames = $this->contextRegistry->getNames();

if ($contextNames) {
$defaultContext = PlatformHelper::getEnv('CASTOR_CONTEXT') ?: $this->contextRegistry->getDefaultName();
$definition = parent::getDefaultInputDefinition();

$this->getDefinition()->addOption(new InputOption(
'context',
'_complete' === $input->getFirstArgument() || 'list' === $input->getFirstArgument() ? null : 'c',
InputOption::VALUE_REQUIRED,
sprintf('The context to use (%s)', implode('|', $contextNames)),
$defaultContext,
$contextNames,
));
}

$this->getDefinition()->addOption(
$definition->addOption(
new InputOption(
'no-remote',
null,
Expand All @@ -137,41 +68,15 @@ private function initializeApplication(InputInterface $input): void
)
);

$this->getDefinition()->addOption(
$definition->addOption(
new InputOption(
'update-remotes',
null,
InputOption::VALUE_NONE,
'Force the update of remote packages',
)
);
}

private function configureContext(InputInterface $input, OutputInterface $output): void
{
try {
$input->bind($this->getDefinition());
} catch (ExceptionInterface) {
// not an issue if parsing gone wrong, we'll just use the default
// context and it will fail later anyway
}

// occurs when running `castor -h`, or if no context is defined
if (!$input->hasOption('context')) {
$this->contextRegistry->setCurrentContext(new Context());

return;
}

$context = $this
->contextRegistry
->get($input->getOption('context'))
;

if ($context->verbosityLevel->isNotConfigured()) {
$context = $context->withVerbosityLevel(VerbosityLevel::fromSymfonyOutput($output));
}

$this->contextRegistry->setCurrentContext($context->withName($input->getOption('context')));
return $definition;
}
}
2 changes: 2 additions & 0 deletions src/Console/ApplicationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Castor\Console\Command\DebugCommand;
use Castor\Console\Command\RepackCommand;
use Castor\Container;
use Castor\Descriptor\DescriptorsCollection;
use Castor\Event\AfterApplicationInitializationEvent;
use Castor\Helper\PathHelper;
use Castor\Helper\PlatformHelper;
Expand Down Expand Up @@ -78,6 +79,7 @@ private static function configureDebug(): ErrorHandler

AbstractCloner::$defaultCasters[self::class] = ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'];
AbstractCloner::$defaultCasters[AfterApplicationInitializationEvent::class] = ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'];
AbstractCloner::$defaultCasters[DescriptorsCollection::class] = ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'];

return $errorHandler;
}
Expand Down
12 changes: 10 additions & 2 deletions src/Console/Command/RepackCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@

namespace Castor\Console\Command;

use Castor\Function\FunctionFinder;
use Castor\Helper\PathHelper;
use Castor\Import\Importer;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;

/** @internal */
class RepackCommand extends Command
{
public function __construct(
#[Autowire(lazy: true)]
private readonly Importer $importer,
) {
parent::__construct();
}

protected function configure(): void
{
$this
Expand Down Expand Up @@ -94,7 +102,7 @@ class RepackedApplication extends Application
$boxConfig['files'] = [
...array_map(
fn (string $file): string => str_replace(PathHelper::getRoot() . '/', '', $file),
FunctionFinder::$files,
$this->importer->getImports(),
),
...$boxConfig['files'] ?? [],
];
Expand Down
3 changes: 0 additions & 3 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
use Castor\Console\Application;
use Castor\Console\Output\SectionOutput;
use Castor\Fingerprint\FingerprintHelper;
use Castor\Function\FunctionFinder;
use Castor\Helper\Notifier;
use Castor\Helper\Waiter;
use Castor\Import\Importer;
use Castor\Import\Kernel;
use Castor\Runner\ParallelRunner;
use Castor\Runner\ProcessRunner;
use Castor\Runner\SshRunner;
Expand Down Expand Up @@ -38,7 +36,6 @@ public function __construct(
public readonly EventDispatcherInterface $eventDispatcher,
public readonly Filesystem $fs,
public readonly FingerprintHelper $fingerprintHelper,
public readonly FunctionFinder $functionFinder,
public readonly HttpClientInterface $httpClient,
public readonly Importer $importer,
public readonly InputInterface $input,
Expand Down
22 changes: 22 additions & 0 deletions src/Descriptor/DescriptorsCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Castor\Descriptor;

final class DescriptorsCollection
{
/**
* @param list<ContextDescriptor> $contextDescriptors
* @param list<ContextGeneratorDescriptor> $contextGeneratorDescriptors
* @param list<ListenerDescriptor> $listenerDescriptors
* @param list<TaskDescriptor> $taskDescriptors
* @param list<SymfonyTaskDescriptor> $symfonyTaskDescriptors
*/
public function __construct(
public readonly array $contextDescriptors,
public readonly array $contextGeneratorDescriptors,
public readonly array $listenerDescriptors,
public readonly array $taskDescriptors,
public readonly array $symfonyTaskDescriptors,
) {
}
}
1 change: 1 addition & 0 deletions src/Descriptor/ListenerDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Castor\Attribute\AsListener;

/** @internal */
class ListenerDescriptor
{
public function __construct(
Expand Down
1 change: 0 additions & 1 deletion src/Descriptor/SymfonyTaskDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Castor\Attribute\AsSymfonyTask;

/** @internal */
class SymfonyTaskDescriptor
{
/**
Expand Down
1 change: 0 additions & 1 deletion src/Descriptor/TaskDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Castor\Attribute\AsTask;

/** @internal */
class TaskDescriptor
{
public function __construct(
Expand Down
17 changes: 7 additions & 10 deletions src/Descriptor/TaskDescriptorCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@

namespace Castor\Descriptor;

trigger_deprecation('castor', '0.16', 'The "%s" class is deprecated, use "%s" instead.', TaskDescriptorCollection::class, DescriptorsCollection::class);

/**
* @deprecated since Castor 0.16, use DescriptorsCollection instead
*/
class TaskDescriptorCollection
{
/**
* @param TaskDescriptor[] $taskDescriptors
* @param SymfonyTaskDescriptor[] $symfonyTaskDescriptors
* @param list<TaskDescriptor> $taskDescriptors
* @param list<SymfonyTaskDescriptor> $symfonyTaskDescriptors
*/
public function __construct(
public readonly array $taskDescriptors = [],
public readonly array $symfonyTaskDescriptors = [],
) {
}

public function merge(self $other): self
{
return new self(
[...$this->taskDescriptors, ...$other->taskDescriptors],
[...$this->symfonyTaskDescriptors, ...$other->symfonyTaskDescriptors],
);
}
}
3 changes: 3 additions & 0 deletions src/Event/AfterApplicationInitializationEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
use Castor\Console\Application;
use Castor\Descriptor\TaskDescriptorCollection;

/**
* @deprecated since Castor 0.16, use FunctionsResolvedEvent instead
*/
class AfterApplicationInitializationEvent
{
public function __construct(
Expand Down
Loading

0 comments on commit 7da8e06

Please sign in to comment.