Updated bootstrap to call packages Bootstrap
The bootstrap procedure has been updated to search through all packages to get the ones with an lotgd-namespace extra field. These are then tested if they have or have not a bootstrap class implementing BootstrapInterface. If yes, they get added to a stack used to modify the bootstrap procedure. For know, bootstrap supports additional entity directories.
This commit is contained in:
+166
-45
@@ -3,19 +3,31 @@ declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\AnsiQuoteStrategy;
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Doctrine\ORM\ {
|
||||
EntityManager,
|
||||
EntityManagerInterface,
|
||||
Mapping\AnsiQuoteStrategy,
|
||||
Tools\Setup,
|
||||
Tools\SchemaTool
|
||||
};
|
||||
use Monolog\ {
|
||||
Logger,
|
||||
Handler\RotatingFileHandler
|
||||
};
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
use LotGD\Core\Exceptions\ArgumentException;
|
||||
use LotGD\Core\Exceptions\InvalidConfigurationException;
|
||||
use LotGD\Core\ {
|
||||
ComposerManager,
|
||||
BootstrapInterface,
|
||||
Exceptions\ArgumentException,
|
||||
Exceptions\InvalidConfigurationException
|
||||
};
|
||||
|
||||
class Bootstrap
|
||||
{
|
||||
private $game;
|
||||
private $annotationDirectories = [];
|
||||
|
||||
/**
|
||||
* Create a new Game object, with all the necessary configuration.
|
||||
* @throws InvalidConfigurationException
|
||||
@@ -23,59 +35,168 @@ class Bootstrap
|
||||
*/
|
||||
public static function createGame(): Game
|
||||
{
|
||||
$configFilePath = getenv('LOTGD_CONFIG');
|
||||
if ($configFilePath === false || strlen($configFilePath) == 0 || is_file($configFilePath) === false) {
|
||||
throw new InvalidConfigurationException("Invalid or missing configuration file: '{$configFilePath}'.");
|
||||
}
|
||||
$config = new Configuration($configFilePath);
|
||||
|
||||
$logger = new Logger('lotgd');
|
||||
// Add lotgd as the prefix for the log filenames.
|
||||
$logger->pushHandler(new RotatingFileHandler($config->getLogPath() . DIRECTORY_SEPARATOR . 'lotgd', 14));
|
||||
|
||||
$v = Game::getVersion();
|
||||
$logger->info("Bootstrap constructing game (Daenerys 🐲{$v}).");
|
||||
|
||||
$pdo = new \PDO($config->getDatabaseDSN(), $config->getDatabaseUser(), $config->getDatabasePassword());
|
||||
|
||||
$configuration = Setup::createAnnotationMetadataConfiguration(Bootstrap::generateAnnotationDirectories($logger, new ComposerManager($logger)), true);
|
||||
$game = new self();
|
||||
return $game->getGame();
|
||||
}
|
||||
|
||||
public function getGame()
|
||||
{
|
||||
$config = $this->createConfiguration();
|
||||
$logger = $this->createLogger($config, "lotgd");
|
||||
$composer = $this->createComposer($logger);
|
||||
$bootstrapClasses = $this->getBootstrapClasses($composer);
|
||||
|
||||
$pdo = $this->connectToDatabase($config);
|
||||
$entityManager = $this->createEntityManager($pdo, $bootstrapClasses);
|
||||
|
||||
$eventManager = $this->createEventManager($entityManager);
|
||||
|
||||
return new Game($config, $logger, $entityManager, $eventManager);
|
||||
}
|
||||
|
||||
protected function createEntityManager(\PDO $pdo, array $bootstrapClasses): EntityManagerInterface
|
||||
{
|
||||
$this->annotationDirectories = $this->generateAnnotationDirectories($bootstrapClasses);
|
||||
$configuration = Setup::createAnnotationMetadataConfiguration($this->annotationDirectories, true);
|
||||
|
||||
// Set a quote
|
||||
$configuration->setQuoteStrategy(new AnsiQuoteStrategy());
|
||||
|
||||
// Create entity manager
|
||||
$entityManager = EntityManager::create(["pdo" => $pdo], $configuration);
|
||||
|
||||
// Create Schema
|
||||
// Create Schema and update database if needed
|
||||
$metaData = $entityManager->getMetadataFactory()->getAllMetadata();
|
||||
$schemaTool = new SchemaTool($entityManager);
|
||||
$schemaTool->updateSchema($metaData);
|
||||
|
||||
return $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a database using pdo
|
||||
* @param \LotGD\Core\Configuration $config
|
||||
* @return \PDO
|
||||
*/
|
||||
protected function connectToDatabase(Configuration $config): \PDO
|
||||
{
|
||||
return new \PDO($config->getDatabaseDSN(), $config->getDatabaseUser(), $config->getDatabasePassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an instance of ComposerManager
|
||||
* @param Logger $logger
|
||||
* @return ComposerManager
|
||||
*/
|
||||
protected function createComposer(Logger $logger): ComposerManager
|
||||
{
|
||||
$composer = new ComposerManager($logger);
|
||||
|
||||
return $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all bootstrap classes
|
||||
* @param ComposerManager $composer
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getBootstrapClasses(ComposerManager $composer): array
|
||||
{
|
||||
$packages = $composer->getPackages();
|
||||
$classes = [];
|
||||
|
||||
foreach ($packages as $package) {
|
||||
if (isset($package->getExtra()["lotgd-namespace"]) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cn = $package->getExtra()["lotgd-namespace"] . "Bootstrap";
|
||||
|
||||
// silently ignore that class does not exist, could be one that doesn't need to bootstrap
|
||||
if (class_exists($cn, true) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cl = new $cn();
|
||||
|
||||
if ($cl instanceof BootstrapInterface) {
|
||||
$classes[] = $cl;
|
||||
}
|
||||
else {
|
||||
$name = $package->getName() . "@" . $package->getVersion();
|
||||
throw new \Exception("Package {$name} does not implement BootstrapInterface in it's Bootstrap class");
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object reading from LOTGD_CONFIG
|
||||
* @return \LotGD\Core\Configuration
|
||||
* @throws InvalidConfigurationException
|
||||
*/
|
||||
protected function createConfiguration(): Configuration
|
||||
{
|
||||
$configFilePath = getenv('LOTGD_CONFIG');
|
||||
if ($configFilePath === false || strlen($configFilePath) == 0 || is_file($configFilePath) === false) {
|
||||
throw new InvalidConfigurationException("Invalid or missing configuration file: '{$configFilePath}'.");
|
||||
}
|
||||
|
||||
$config = new Configuration($configFilePath);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a logger instance
|
||||
* @param type $name
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
protected function createLogger(Configuration $config, string $name): LoggerInterface
|
||||
{
|
||||
$logger = new Logger($name);
|
||||
// Add lotgd as the prefix for the log filenames.
|
||||
$logger->pushHandler(new RotatingFileHandler($config->getLogPath() . DIRECTORY_SEPARATOR . $name, 14));
|
||||
|
||||
$eventManager = new EventManager($entityManager);
|
||||
|
||||
return new Game($config, $entityManager, $eventManager, $logger);
|
||||
$v = Game::getVersion();
|
||||
$logger->info("Bootstrap constructing game (Daenerys 🐲{$v}).");
|
||||
|
||||
return $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an instance of the EventManager
|
||||
* @param EntityManagerInterface $entityManager
|
||||
* @return \LotGD\Core\EventManager
|
||||
*/
|
||||
protected function createEventManager(EntityManagerInterface $entityManager): EventManager
|
||||
{
|
||||
return new EventManager($entityManager);
|
||||
}
|
||||
|
||||
public static function generateAnnotationDirectories(Logger $logger, ComposerManager $manager): array
|
||||
/**
|
||||
* Is used to get all directories used to generate annotations.
|
||||
* @param array $bootstrapClasses
|
||||
* @return array
|
||||
*/
|
||||
protected function generateAnnotationDirectories(array $bootstrapClasses): array
|
||||
{
|
||||
// Read db annotations from our own model files.
|
||||
$directories = [__DIR__ . '/Models'];
|
||||
|
||||
// Find other annotation directories from installed packages.
|
||||
$packages = $manager->getPackages();
|
||||
foreach ($packages as $p) {
|
||||
$name = $p->getName();
|
||||
$extra = $p->getExtra();
|
||||
if (!empty($extra['lotgd-namespace'])) {
|
||||
$namespace = $extra['lotgd-namespace'];
|
||||
$path = $manager->translateNamespaceToPath($namespace);
|
||||
|
||||
if ($path === null) {
|
||||
throw new \Exception("Cannot load classes in the namespace {$namespace} from package {$name}.");
|
||||
}
|
||||
|
||||
$directories[] = $path;
|
||||
|
||||
// Get additional annotation directories from bootstrap classes
|
||||
foreach ($bootstrapClasses as $bootstrap) {
|
||||
if ($bootstrap->hasEntityPath()) {
|
||||
$directories[] = $bootstrap->getEntityPath();
|
||||
}
|
||||
}
|
||||
|
||||
return $directories;
|
||||
}
|
||||
|
||||
public function getReadAnnotationDirectories(): array
|
||||
{
|
||||
return $this->annotationDirectories;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core;
|
||||
|
||||
interface BootstrapInterface
|
||||
{
|
||||
public function hasEntityPath(): bool;
|
||||
public function getEntityPath(): string;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class ComposerManager
|
||||
|
||||
/**
|
||||
* Return all the packages installed in the current setup.
|
||||
* @return array Array of \Composer\PackageInterface
|
||||
* @return array<Composer\PackageInterface>
|
||||
*/
|
||||
public function getPackages(): array
|
||||
{
|
||||
|
||||
+5
-5
@@ -16,17 +16,17 @@ class Game
|
||||
private $moduleManager;
|
||||
private $logger;
|
||||
private $configuration;
|
||||
|
||||
|
||||
public function __construct(
|
||||
Configuration $configuration,
|
||||
Logger $logger,
|
||||
EntityManagerInterface $entityManager,
|
||||
EventManager $eventManager,
|
||||
Logger $logger)
|
||||
{
|
||||
EventManager $eventManager
|
||||
) {
|
||||
$this->configuration = $configuration;
|
||||
$this->logger = $logger;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->eventManager = $eventManager;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+23
-15
@@ -10,7 +10,7 @@ use Monolog\Handler\NullHandler;
|
||||
|
||||
use LotGD\Core\Bootstrap;
|
||||
use LotGD\Core\ComposerManager;
|
||||
use LotGD\Core\Tests\FakeModule\UserEntity;
|
||||
use LotGD\Core\Tests\FakeModule\Models\UserEntity;
|
||||
|
||||
class BootstrapTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@@ -42,19 +42,27 @@ class BootstrapTest extends \PHPUnit_Framework_TestCase
|
||||
'lotgd-namespace' => 'LotGD\\Core\\Tests\\FakeModule\\',
|
||||
));
|
||||
$composerManager->method('getPackages')->willReturn(array($package));
|
||||
|
||||
$expected = __DIR__ . DIRECTORY_SEPARATOR . 'FakeModule';
|
||||
$composerManager->method('translateNamespaceToPath')->willReturn($expected);
|
||||
|
||||
$result = Bootstrap::generateAnnotationDirectories($this->logger, $composerManager);
|
||||
|
||||
$string = implode(', ', $result);
|
||||
$found = false;
|
||||
foreach ($result as $r) {
|
||||
if (realpath($r) == $expected) {
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($found, "Annotation directories [{$string}] does not contain {$expected}.");
|
||||
|
||||
$bootstrap = $this->getMockBuilder(Bootstrap::class)
|
||||
->setMethods(["createComposer"])
|
||||
->getMock();
|
||||
|
||||
$bootstrap->method("createComposer")->willReturn($composerManager);
|
||||
|
||||
$game = $bootstrap->getGame();
|
||||
|
||||
$this->assertGreaterThanOrEqual(2, $bootstrap->getReadAnnotationDirectories());
|
||||
|
||||
$user = new UserEntity();
|
||||
$user->setName("Monthy");
|
||||
$game->getEntityManager()->persist($user);
|
||||
$game->getEntityManager()->flush();
|
||||
$id = $user->getId();
|
||||
$this->assertInternalType("int", $id);
|
||||
$game->getEntityManager()->clear();
|
||||
$user = $game->getEntityManager()->getRepository(UserEntity::class)->find($id);
|
||||
$this->assertInternalType("int", $user->getId());
|
||||
$this->assertInternalType("string", $user->getName());
|
||||
$this->assertSame("Monthy", $user->getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace LotGD\Core\Tests\FakeModule;
|
||||
|
||||
use LotGD\Core\BootstrapInterface;
|
||||
|
||||
class Bootstrap implements BootstrapInterface
|
||||
{
|
||||
public function hasEntityPath(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getEntityPath(): string
|
||||
{
|
||||
return __DIR__ . "/Models";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LotGD\Core\Tests\FakeModule\Models;
|
||||
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="Users")
|
||||
*/
|
||||
class UserEntity
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
/** @Column(type="string", length=50); */
|
||||
private $name;
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user