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:
Vassyli
2016-07-26 15:41:48 +02:00
parent 86b5f075fa
commit 58147ed14b
7 changed files with 257 additions and 66 deletions
+166 -45
View File
@@ -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;
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace LotGD\Core;
interface BootstrapInterface
{
public function hasEntityPath(): bool;
public function getEntityPath(): string;
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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());
}
}
+18
View File
@@ -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";
}
}
+34
View File
@@ -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;
}
}