Commit 2227b0ba authored by Avris's avatar Avris

Merge branch 'v4.0' into 'master'

v4.0

See merge request !1
parents 7f628e36 b57c2ae9
tests/_output/*
vendor/*
composer.lock
.idea/*
/.idea/*
**/.DS_Store
/vendor/*
/tests/_output/*
## Micrus Doctrine Bridge ##
# Micrus Doctrine Bridge ##
This is a module for [Micrus framework](https://micrus.avris.it) that allows you
to integrate it with [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html).
To install this module, open the file `app/Config/modules.yml` and add:
## Installation
- Avris\Micrus\Doctrine\DoctrineModule
Then run:
Run:
composer require avris/micrus-doctrine
### Example usage ###
<?php
namespace App\Model;
use Avris\Micrus\Controller\Http\UploadedFile;
use Avris\Micrus\Forms\Widget\ChoiceHelper;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="post")
* @ORM\HasLifecycleCallbacks
**/
class Post
{
/**
* @var integer
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string
* @ORM\Column(type="string")
*/
protected $title;
/**
* @var string
* @ORM\Column(type="text")
*/
protected $content;
/**
* @var \DateTime
* @ORM\Column(type="datetime")
*/
protected $publishedAt;
/**
* @var User
* @ORM\ManyToOne(targetEntity="User", inversedBy="posts")
**/
protected $user;
/**
* @var Category[]|ArrayCollection
* @ORM\ManyToMany(targetEntity="Category", inversedBy="posts")
* @ORM\JoinTable(name="category_post")
**/
protected $categories;
/**
* @var UploadedFile
*/
protected $attachment;
/**
* @var string
* @ORM\Column(type="string", nullable=true)
*/
protected $file;
public function __construct()
{
$this->categories = new ArrayCollection();
$this->publishedAt = new \DateTime();
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @param string $title
* @return $this
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* @param string $content
* @return $this
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
Then register the module in your `App\App:registerModules`:
/**
* @return \DateTime
*/
public function getPublishedAt()
{
return $this->publishedAt;
}
yield new \Avris\Micrus\Doctrine\DoctrineModule;
/**
* @param \DateTime $publishedAt
* @return $this
*/
public function setPublishedAt($publishedAt)
{
$this->publishedAt = $publishedAt;
Finally, run:
return $this;
}
bin/env
You will be asked for a [connection string](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url)
and it will be saved in the `.env` file.
/**
* @return User
*/
public function getUser()
{
return $this->user;
}
## EntityManager
/**
* @param User $user
*/
public function setUser($user)
{
$this->user = $user;
}
`DoctrineManager` will scan all modules for a `/src/Entity` directory and if it's there --
it will automatically register all classes from this directory as doctrine entities.
/**
* @return Category[]|ArrayCollection
*/
public function getCategories()
{
return $this->categories;
}
Doctrine's `EntityManager` is registered in the container as `Doctrine\ORM\EntityManagerInterface`:
/**
* @param Category $category
* @return $this
*/
public function addCategories(Category $category)
{
if (!$this->categories->contains($category)) {
$this->categories->add($category);
}
return $this;
}
/**
* @param Category $category
* @return $this
*/
public function removeCategories(Category $category)
{
$this->categories->removeElement($category);
return $this;
}
/**
* @return UploadedFile
*/
public function getAttachment()
{
return $this->attachment;
}
/**
* @param UploadedFile $attachment
*/
public function setAttachment(UploadedFile $attachment)
{
$this->attachment = $attachment;
}
public function incrementStuffAction(EntityManagerInterface $em)
{
$stuff = $em->getRepository('Stuff')->findBySomeCustomCondition();
$stuff->incrementValue();
$em->persist($stuff);
$em->flush();
$this->addFlash('success', 'Stuff incremented successfully');
/**
* @return string
*/
public function getFile()
{
return $this->file;
}
return $this->redirectToRoute('home');
}
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*/
public function handleFileUpload()
{
if (!$this->attachment) {
return;
}
## UserProvider
$filename = substr(md5(uniqid()), 0, 8) . '.' . $this->attachment->getExtension();
if ($this->attachment->moveTo('run/att/' . $filename)) {
$this->file = $filename;
}
}
}
This module registers Doctrine as the `UserProvider`.
It will try to find a user from `App\Entity\User` repository using `email` as identifier.
To modify those values, just use the container:
**Note the convention** that is important for binding on object to a form and displaying obejct's values in Twig templates:
if an attribute `$fieldName` is not public, it will be accessed using `getFieldName` and `setFieldName`, or if it's an array:
`getFieldName`, `addFieldName`, `removeFieldName` and `hasFieldName`.
Avris\Micrus\Doctrine\DoctrineUserProvider:
arguments:
$column: username
You can use Doctrine's EntityManager, retrieving it with `$this->getEm()` (in controllers) or `$container->get('orm')->getEntityManager()`.
You can fetch repositories simply by the entity's name:
## MatchProvider
public function incrementStuffAction()
{
$stuff = $this->getEm()->getRepository('Stuff')->findBySomeCustomCondition();
$stuff->incrementValue();
$this->getEm()->persist($stuff);
$this->getEm()->flush();
$this->addFlash('success', 'Stuff incremented successfully');
Doctrine will automatically match the arguments of your controller if they are entities, for example:
return $this->redirectToRoute('home');
}
/**
* @M\Route("/post/{int:id}/read")
*/
public function readAction(Post $post) { }
For request `GET /post/8/read` will try to find `App\Entity\Post` with `id = 8`
and either return it or throw a `NotFoundHttpException` if failed.
### Console ###
## Console
Some Doctrine console commands are available in `bin/micrus` under `db` namespace:
......@@ -264,14 +68,11 @@ Some Doctrine console commands are available in `bin/micrus` under `db` namespac
php bin/micrus db:schema:update
php bin/micrus db:schema:validate
php bin/micrus db:schema:drop
php bin/micrus db:query:sql "SELECT * FROM user"
php bin/micrus db:query:dql "SELECT u FROM App\Model\User u WHERE u.role='ROLE_ADMIN'"
You can also use `php bin/mdoctrine` to access more advanced commands from Doctrine.
### Copyright ###
## Copyright
* **Author:** Andrzej Prusinowski [(Avris.it)](https://avris.it)
* **Author:** Andre Prusinowski [(Avris.it)](https://avris.it)
* **Licence:** [MIT](https://mit.avris.it)
......@@ -11,11 +11,10 @@
"homepage": "https://avris.it"
}],
"require": {
"avris/micrus": "^3.0",
"doctrine/orm": "^2.5"
"avris/micrus": "^4.0",
"doctrine/orm": "^2.6"
},
"autoload": {
"psr-4": { "Avris\\Micrus\\Doctrine\\": "src" }
},
"bin": ["mdoctrine"]
}
}
Avris\Micrus\Doctrine\:
dir: '%MODULE_DIR%/src/'
Avris\Micrus\Doctrine\DoctrineManager:
arguments:
$proxyDir: '%CACHE_DIR%/doctrine'
Avris\Micrus\Model\User\UserProviderInterface: Avris\Micrus\Doctrine\DoctrineUserProvider
Avris\Micrus\Doctrine\DoctrineUserProvider:
public: true
Doctrine\ORM\EntityManagerInterface:
resolve: '@Avris\Micrus\Doctrine\DoctrineManager.entityManager'
public: true
#!/usr/bin/env php
<?php
use Avris\Micrus\Bootstrap\App;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Symfony\Component\Console\Application;
set_time_limit(0);
foreach (['', '/..', '/../..', '/../../..'] as $dir) {
if (file_exists($autoloader = __DIR__ . $dir . '/vendor/autoload.php')) {
require_once $autoloader;
break;
}
}
/** @var EntityManager $em */
$em = (new App('cli', dirname(dirname($autoloader))))->getContainer()->get('orm')->getEntityManager();
$app = new Application('Doctrine');
$helperSet = $app->getHelperSet();
$helperSet->set(new ConnectionHelper($em->getConnection()), 'db');
$helperSet->set(new EntityManagerHelper($em), 'em');
ConsoleRunner::addCommands($app);
$app->run();
\ No newline at end of file
<?php
namespace Avris\Micrus\Doctrine;
use Avris\Micrus\Console\ConsoleEvent;
use Avris\Micrus\Model\ModelDirectory;
use Avris\Micrus\Model\ORM;
use Avris\Bag\Bag;
use Avris\Micrus\Tool\Logger;
use Avris\Micrus\Tool\Config\ParametersProvider;
use Doctrine\Common\Cache\ClearableCache;
use Doctrine\Common\Proxy\Autoloader;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\ORM\Cache;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Doctrine\ORM\Tools\Setup;
class Doctrine implements ORM, ParametersProvider
{
/** @var EntityManager */
protected $entityManager;
/** @var string */
protected $cacheDir;
/**
* @param Logger $logger
* @param string $env
* @param string $rootDir
* @param array $db
* @param ModelDirectory[] $modelDirectories
* @param Cache $cache
* @throws \Doctrine\ORM\ORMException
*/
public function __construct(Logger $logger, $env, $rootDir, $db, array $modelDirectories, Cache $cache = null)
{
if (empty($db)) {
$logger->warning('No ORM configuration');
$db = ['driver' => 'pdo_sqlite', 'memory' => true];
}
$logger->debug(sprintf('Doctrine ORM, database driver: %s', $db['driver']));
$this->cacheDir = $rootDir . '/run/cache/' . $env . '/doctrine';
$dirs = [$rootDir . '/app/Model'];
foreach ($modelDirectories as $dir) {
$dirs[] = $dir->getDirectory();
}
$config = Setup::createAnnotationMetadataConfiguration(
$dirs,
$env == 'dev',
$this->cacheDir,
$cache,
false
);
$config->setRepositoryFactory(new MicrusRepositoryFactory());
$config->setSQLLogger(new DoctrineLogger($logger));
if (empty($db['charset'])) {
$db['charset'] = 'utf8';
}
$this->entityManager = EntityManager::create($db, $config);
$warmup = $this->onCacheWarmup();
if ($warmup) {
$logger->notice($warmup);
}
}
public function getEntityManager()
{
return $this->entityManager;
}
public function find($type, $id)
{
return $this->entityManager->getRepository($type)->find($id);
}
public function findAll($type)
{
return $this->entityManager->getRepository($type)->findAll();
}
public function findBy($type, $attribute, $value)
{
return $this->entityManager->getRepository($type)->findBy([$attribute => $value]);
}
public function findOneBy($type, $attribute, $value)
{
return $this->entityManager->getRepository($type)->findOneBy([$attribute => $value]);
}
public function onCacheClear()
{
$config = $this->entityManager->getConfiguration();
$drivers = [
$config->getQueryCacheImpl(),
$config->getMetadataCacheImpl(),
$config->getHydrationCacheImpl(),
];
foreach ($drivers as $driver) {
if ($driver instanceof ClearableCache) {
$driver->deleteAll();
}
}
}
public function onCacheWarmup()
{
if (file_exists($this->cacheDir)) {
return false;
}
mkdir($this->cacheDir, 0777, true);
$metadatas = $this->entityManager->getMetadataFactory()->getAllMetadata();
$this->entityManager->getProxyFactory()->generateProxyClasses($metadatas, $this->cacheDir);
return sprintf(
'Metadata for %s generated in %s',
implode(', ', array_map(function($el) { return $el->name; }, $metadatas)),
$this->cacheDir
);
}
public function onConsole(ConsoleEvent $event)
{
Autoloader::register($this->cacheDir, 'DoctrineProxies');
if (!$this->entityManager) {
return;
}
$app = $event->getApp();
$helperSet = $app->getHelperSet();
$helperSet->set(new ConnectionHelper($this->entityManager->getConnection()), 'db');
$helperSet->set(new EntityManagerHelper($this->entityManager), 'em');
$app->add((new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand())->setName('db:query:sql'));
$app->add((new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand())->setName('db:query:dql'));
$app->add((new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand())->setName('db:schema:create'));
$app->add((new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand())->setName('db:schema:update'));
$app->add((new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand())->setName('db:schema:drop'));
$app->add((new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand())->setName('db:schema:validate'));
$app->add((new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand())->setName('db:generate:proxies'));
}
public function getDefaultParameters()
{
return [
'database' => [
'driver' => 'pdo_mysql',
'host' => '127.0.0.1',
'dbname' => 'micrus',
'user' => 'root',
'password' => null,
'charset' => 'utf8',
],
];
}
}
<?php
namespace Avris\Micrus\Doctrine;
use Avris\Micrus\Console\Task\AbstractFixturesTask;
abstract class DoctrineFixturesTask extends AbstractFixturesTask
{
protected function truncateDatabase()
{
$connection = $this->em->getConnection();
$platform = $connection->getDatabasePlatform();
$query = 'SET FOREIGN_KEY_CHECKS = 0;';
foreach ($connection->getSchemaManager()->listTables() as $table) {
$query .= $platform->getTruncateTableSQL($table->getName(), true) . ';';
}
$query .= 'SET FOREIGN_KEY_CHECKS = 1;';
$connection->executeQuery($query);
}
}
<?php
namespace Avris\Micrus\Doctrine;
use Avris\Micrus\Tool\Logger;
use Doctrine\DBAL\Logging\SQLLogger;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
class DoctrineLogger implements SQLLogger
final class DoctrineLogger implements SQLLogger
{
/** @var Logger */
protected $logger;
/** @var LoggerInterface */
private $logger;
public function __construct(Logger $logger)
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
$this->logger = $logger instanceof Logger ? $logger->withName('doctrine') : $logger;
}
public function startQuery($sql, array $params = null, array $types = null)
{
$this->logger->notice('[SQL] ' . $sql . ' | ' . @json_encode($params) . ' | ' . @json_encode($types));
$this->logger->notice($sql, $params ?: []);
}
public function stopQuery()
{
}
}
<?php
namespace Avris\Micrus\Doctrine;
use Avris\Dispatcher\EventSubscriberInterface;
use Avris\Micrus\Bootstrap\ModuleInterface;
use Avris\Micrus\Console\ConsoleWarmupEvent;
use Doctrine\Common\Cache\ClearableCache;
use Doctrine\Common\Proxy\Autoloader;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\ORM\Cache;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Doctrine\ORM\Tools\Setup;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\Tools\Console\Command as ORMCommand;
use Doctrine\DBAL\Tools\Console\Command as DBALCommand;
final class DoctrineManager implements EventSubscriberInterface
{
/** @var EntityManager */
private $entityManager;
public function __construct(
string $envDatabaseUrl,
bool $envAppDebug,
string $proxyDir,
array $modules,
LoggerInterface $logger,
DoctrineLogger $doctrineLogger,
Cache $cache = null
) {
$dirs = [];
/** @var ModuleInterface $module */
foreach (array_reverse($modules) as $module) {
if (is_dir($dir = $module->getDir() . '/src/Entity')) {
$dirs[] = $dir;
}
}
$config = Setup::createAnnotationMetadataConfiguration($dirs, $envAppDebug, $proxyDir, $cache, false);
$config->setSQLLogger($doctrineLogger);
$this->entityManager = EntityManager::create(['url' => $envDatabaseUrl], $config);
if ($warmupMessage = $this->warmupCache()) {
$logger->notice($warmupMessage);
}
}
public function getEntityManager()
{
return $this->entityManager;
}
public function clearCache()
{
$config = $this->entityManager->getConfiguration();
$drivers = [
$config->getQueryCacheImpl(),
$config->getMetadataCacheImpl(),
$config->getHydrationCacheImpl(),
];
foreach ($drivers as $driver) {
if ($driver instanceof ClearableCache) {
$driver->deleteAll();
}
}
}
public function warmupCache()
{
$proxyDir = $this->entityManager->getConfiguration()->getProxyDir();
if (file_exists($proxyDir)) {
return false;
}
mkdir($proxyDir, 0777, true);
$metadatas = $this->entityManager->getMetadataFactory()->getAllMetadata();
$this->entityManager->getProxyFactory()->generateProxyClasses($metadatas, $proxyDir);
return sprintf(
'Metadata for %s generated in %s',
implode(', ', array_map(function($el) { return $el->name; }, $metadatas)),
$proxyDir
);
}
public function registerCommands(ConsoleWarmupEvent $event)
{
$proxyDir = $this->entityManager->getConfiguration()->getProxyDir();
Autoloader::register($proxyDir, 'DoctrineProxies');
$app = $event->getApp();
$helperSet = $app->getHelperSet();
$helperSet->set(new ConnectionHelper($this->entityManager->getConnection()), 'db');
$helperSet->set(new EntityManagerHelper($this->entityManager), 'em');
$app->add((new DBALCommand\RunSqlCommand())->setName('db:query:sql'));
$app->add((new ORMCommand\RunDqlCommand())->setName('db:query:dql'));
$app->add((new ORMCommand\SchemaTool\CreateCommand())->setName('db:schema:create'));
$app->add((new ORMCommand\SchemaTool\UpdateCommand())->setName('db:schema:update'));
$app->add((new ORMCommand\SchemaTool\DropCommand())->setName('db:schema:drop'));
$app->add((new ORMCommand\ValidateSchemaCommand())->setName('db:schema:validate'));
}
public function getSubscribedEvents(): iterable
{
yield 'consoleWarmup' => [$this, 'registerCommands'];
yield 'cacheWarmup' => [$this, 'warmupCache'];
yield 'cacheClear' => [$this, 'clearCache'];
}
}
<?php
namespace Avris\Micrus\Doctrine;
use Avris\Micrus\Exception\Http\NotFoundHttpException;
use Avris\Micrus\Model\MatchProvider;
use Avris\Bag\QueueBag;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\MappingException;
final class DoctrineMatchProvider implements MatchProvider
{
/** @var EntityManager */