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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/translations/modules/*
/translations/override/*
/tests/_local/
/config/modules.yaml
###< syncengine ###

###> symfony/framework-bundle ###
Expand Down
5 changes: 4 additions & 1 deletion bin/console
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#!/usr/bin/env php
<?php

use SyncEngine\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use SyncEngine\Kernel;

if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}

if ( file_exists( dirname(__DIR__).'/modules/registry.php' ) ) {
Comment thread
JoryHogeveen marked this conversation as resolved.
require_once dirname(__DIR__).'/modules/registry.php';
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
Expand Down
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@
},
"autoload": {
"psr-4": {
"SyncEngine\\": "src/",
"SyncEngine\\Module\\": "modules/"
"SyncEngine\\": "src/"
}
},
"autoload-dev": {
Expand Down
2 changes: 2 additions & 0 deletions config/modules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

services:
7 changes: 1 addition & 6 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

imports:
- { resource: 'icons.yaml' }
- { resource: 'modules.yaml' }

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
Expand Down Expand Up @@ -45,12 +46,6 @@ services:
- '../src/Model/'
- '../src/Kernel.php'

# Register module files as services.
SyncEngine\Module\:
resource: '%syncengine.dir.modules%'
autowire: true
lazy: false

# Register module route loader.
SyncEngine\Routing\ModuleLoader:
tags: [routing.loader]
Expand Down
3 changes: 3 additions & 0 deletions public/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

use SyncEngine\Kernel;

if ( file_exists( dirname(__DIR__).'/modules/registry.php' ) ) {
Comment thread
JoryHogeveen marked this conversation as resolved.
require_once dirname(__DIR__).'/modules/registry.php';
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
Expand Down
58 changes: 39 additions & 19 deletions src/Controller/Admin/ModuleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Constraints\File;
use SyncEngine\Attribute\MenuItem;
use SyncEngine\Model\ModuleModel;
use SyncEngine\Service\Provider\Modules;
use SyncEngine\Service\System;

class ModuleController extends AdminController
{

public function __construct(
private readonly System $system, private readonly KernelInterface $kernel,
) {}

#[Route( '/json/module/{vendor}/{module}', name: 'json_module' )]
public function moduleJson( string $vendor, string $module, Request $request, Modules $modules ): Response
{
Expand Down Expand Up @@ -53,7 +60,7 @@ public function index( Modules $modulesService ): Response
$modules[ $key ] = $module->normalize();

// @todo Move to model?
$ref = new \ReflectionClass( $module );
$ref = new \ReflectionClass( $module );
$modules[ $key ]['_has_admin'] = ( $ref->getMethod( 'renderRequest' )->class === $module::class );
}

Expand Down Expand Up @@ -135,23 +142,32 @@ public function moduleUpload( Request $request, Modules $modulesService ): Respo
#[Route( '/module/install/{vendor}/{moduleName}/{previousVersion}', name: 'module_install_run' )]
public function moduleInstall( string $vendor, string $moduleName, string $previousVersion, Modules $modulesService )
{
$module = $modulesService->get( $vendor . '/' . $moduleName );
$module = $modulesService->get( $vendor . '/' . $moduleName );
$success = false;

if ( ! $previousVersion ) {
try {
$success = $module->install();
$msg = '{moduleName} successfully installed';
} catch ( \Throwable $e ) {
$msg = $e->getMessage();
}
} elseif ( $previousVersion ) {
try {
$success = $module->update( $previousVersion );
$msg = '{moduleName} successfully updated';
} catch ( \Throwable $e ) {
$msg = $e->getMessage();
}
}

if ( $previousVersion == 0 and $module->install() ) {
if ( $success ) {
$this->addFlash(
'success',
$this->trans( 'moduleName successfully installed', [ 'moduleName' => $moduleName ] )
);
} elseif ( $previousVersion != 0 and $module->update( $previousVersion ) ) {
$this->addFlash(
'success',
$this->trans( 'moduleName successfully updated', [ 'moduleName' => $moduleName ] )
$this->trans( $msg, [ 'moduleName' => $moduleName ] )
);
} else {
$this->addFlash(
'warning',
$this->trans( 'Cant run install of moduleName', [ 'moduleName' => $moduleName ] )
);
$this->addFlash( 'warning', $msg );
}

return $this->redirectToRoute( 'syncengine_modules' );
Expand All @@ -175,18 +191,20 @@ public function moduleUninstall( string $vendor, string $module, Request $reques
return $this->redirectToRoute( 'syncengine_modules' );
}

if ( $module->uninstall() ) {
try {
$module->uninstall();
$this->addFlash(
'success',
$this->trans( '{moduleName} successfully uninstalled', [ 'moduleName' => $name ] )
);
} else {
} catch ( \Throwable $e ) {
$this->addFlash( 'warning', $this->trans( 'Uninstall unsuccessful' ) );

return $this->redirectToRoute( 'syncengine_modules' );
}

$this->_deleteModule( $module );
$this->system->runCommand( 'cache:clear' );

return $this->redirectToRoute( 'syncengine_modules' );
}
Expand Down Expand Up @@ -219,8 +237,6 @@ private function _installModule( UploadedFile $zipFile, Modules $modulesService
} else {
$this->addFlash( 'warning', $e->getMessage() );
}

return $this->redirectToRoute( 'syncengine_module_upload' );
}

$tmpModuleDir = $this->_findModuleRoot( $tmpModuleDir );
Expand All @@ -240,9 +256,11 @@ private function _installModule( UploadedFile $zipFile, Modules $modulesService
return $this->redirectToRoute( 'syncengine_module_upload' );
}

$this->system->runCommand( 'cache:clear' );

$modules = $modulesService->getAll();

$modulePath = $moduleInfo['vendor'] . DIRECTORY_SEPARATOR . $moduleInfo['moduleName'];
$modulePath = $moduleInfo['vendor'] . DIRECTORY_SEPARATOR . $moduleInfo['moduleName'];
$moduleLocator = $moduleInfo['vendor'] . '/' . $moduleInfo['moduleName'];

foreach ( $modules as $module ) {
Expand Down Expand Up @@ -274,6 +292,8 @@ private function _installModule( UploadedFile $zipFile, Modules $modulesService
);
}

$this->kernel->generateRegistry();

$this->_deleteTmpDir();

$moduleInfo['previousVersion'] = $previousVersion ?? 0;
Expand Down Expand Up @@ -338,7 +358,7 @@ private function _findModuleRoot( $dir )

private function _parseModuleInfo( $dir ): false|array
{
$files = ( new Finder() )->in( $dir )->files()->name( '*.php' );
$files = ( new Finder() )->in( $dir . "/src" )->files()->name( '*.php' );

$ns = '';
foreach ( $files as $file ) {
Expand Down
117 changes: 107 additions & 10 deletions src/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,117 @@ class Kernel extends BaseKernel
{
use MicroKernelTrait;

private $serviceTemplate = '
SyncEngine\Module\%vendor%\%module%\:
resource: "%kernel.project_dir%/modules/%vendor%/%module%/src"
autowire: true
lazy: false
autoconfigure: true
';

public function generateRegistry()
{
$modulesPath = $this->getProjectDir() . '/modules';
$vendors = $this->getDirectories( $modulesPath );
$register = [];
$services = [];

foreach ( $vendors as $vendor ) {
$vendorPath = $modulesPath . '/' . $vendor;
$modules = $this->getDirectories( $vendorPath );

foreach ( $modules as $module ) {
$autoloadFilePath = $vendorPath . '/' . $module . '/register.php';

if ( file_exists( $autoloadFilePath ) ) {
$register[] = "include __DIR__ . '/" . $vendor . '/' . $module . "/register.php';";
}

if ( is_dir( $vendorPath . '/' . $module . '/src' ) ) {
$services[] = $this->generateServiceEntry( $vendor, $module );
}
}
}

$this->writeRegistryFile( $register );
$this->writeServicesFile( $services );
}

private function getDirectories( string $path ): array
{
return array_filter(
scandir( $path ),
function ( $folder ) use ( $path ) {
return is_dir( $path . '/' . $folder ) && ! in_array( $folder, [ '.', '..' ] );
}
);
}

private function generateServiceEntry( string $vendor, string $module ): string
{
return str_replace(
[ '%vendor%', '%module%' ],
[ $vendor, $module ],
$this->serviceTemplate
);
}

private function writeRegistryFile( array $register )
{
$content = "<?php\n" . implode( "\n", $register );
$this->safeFilePutContents( $this->getProjectDir() . '/modules/registry.php', $content );
}

private function writeServicesFile( array $services )
{
$content = '
services:
' . implode( "\n", $services ) . '
# Autoconfigure service tags based on instances.
_instanceof:
SyncEngine\Model\Abstract\EntityModel:
tags: [ \'syncengine.model.entity\' ]
SyncEngine\Model\ModuleModel:
tags: [ \'syncengine.model.module\' ]
SyncEngine\Model\BlueprintModel:
tags: [ \'syncengine.model.blueprint\' ]
SyncEngine\Model\ColumnModel:
tags: [ \'syncengine.model.column\' ]
SyncEngine\Model\CodecModel:
tags: [ \'syncengine.model.codec\' ]
SyncEngine\Model\TaskModel:
tags: [ \'syncengine.model.task\' ]
SyncEngine\Model\WebserviceModel:
tags: [ \'syncengine.model.webservice\' ]';

// @todo Generate YAML based on array?
$this->safeFilePutContents( $this->getProjectDir() . '/config/modules.yaml', $content );
}

private function safeFilePutContents( string $filePath, string $content )
{
if ( file_put_contents( $filePath, $content, LOCK_EX ) === false ) {
throw new \RuntimeException( "Failed to write module registry to file: {$filePath}" );
}
}

protected function build( ContainerBuilder $container ): void
{
$this->generateRegistry();

parent::build( $container );

$container->registerAttributeForAutoconfiguration(MenuItem::class, static function (
ChildDefinition $definition,
MenuItem $attribute,
\ReflectionClass|\ReflectionMethod $reflector
): void {
$args = [];
if ( $reflector instanceof \ReflectionMethod ) {
$args['method'] = $reflector->getName();
$container->registerAttributeForAutoconfiguration(
MenuItem::class,
static function (
ChildDefinition $definition, MenuItem $attribute, \ReflectionClass|\ReflectionMethod $reflector
): void {
$args = [];
if ( $reflector instanceof \ReflectionMethod ) {
$args['method'] = $reflector->getName();
}
$definition->addTag( 'syncengine.attribute.menuitem', $args );
}
$definition->addTag( 'syncengine.attribute.menuitem', $args );
});
);
}
}