Skip to content

Instantly share code, notes, and snippets.

@FaKleiser
Last active November 16, 2024 09:38
Show Gist options
  • Save FaKleiser/a8b2c98586f2b939890fc448eda70f91 to your computer and use it in GitHub Desktop.
Save FaKleiser/a8b2c98586f2b939890fc448eda70f91 to your computer and use it in GitHub Desktop.
CompactBundle for DDD applications with Symfony. See this blog post for more information: http://www.fabian-kleiser.de/blog/domain-driven-design-with-symfony-a-folder-structure
<?php
namespace Fk\Core\Infrastructure\Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* The compact bundle combines a bundle definition with an {@link ExtensionInterface} to provide sound defaults for
* implementing custom bundles.
*/
abstract class CompactBundle extends Bundle
{
/**
* {@inheritdoc}
*/
public function getContainerExtension()
{
return new CompactBundleExtension($this);
}
}
<?php
namespace Fk\Core\Infrastructure\Bundle;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
/**
* Provides an {@link ExtensionInterface} and {@link PrependExtensionInterface} implementation for {@link
* CompactBundle}s. Defines conventions to be used among bundles.
*/
class CompactBundleExtension implements ExtensionInterface, PrependExtensionInterface
{
/** @var CompactBundle the associated bundle */
private $bundle;
public function __construct(CompactBundle $bundle)
{
$this->bundle = $bundle;
}
/**
* {@inheritdoc}
*/
public function load(array $config, ContainerBuilder $container)
{
$configDir = new FileLocator($this->bundle->getPath() . '/Resources/config');
$loader = new YamlFileLoader($container, $configDir);
$loader->load("services.yml");
}
/**
* {@inheritdoc}
*/
public function getAlias()
{
return Container::underscore($this->bundle->getName());
}
/**
* {@inheritdoc}
*/
public function getXsdValidationBasePath()
{
return false;
}
/**
* {@inheritdoc}
*/
public function getNamespace()
{
return 'http://example.org/schema/dic/' . $this->getAlias();
}
/**
* {@inheritdoc}
*/
public function prepend(ContainerBuilder $container)
{
$this->prependDoctrineMappingConfiguration($container);
$this->prependJmsSerializerConfiguration($container);
}
/**
* Configures doctrine to add mapping files if doctrine config files are stored within this compact bundle in:
* <Bundle>/Resources/config/doctrine
*
* @param ContainerBuilder $container
*/
private function prependDoctrineMappingConfiguration(ContainerBuilder $container)
{
$doctrineConfigs = sprintf('%s/Resources/config/doctrine', $this->bundle->getPath());
if (is_dir($doctrineConfigs)) {
$ns = $this->bundle->getNamespace();
$container->prependExtensionConfig('doctrine', [
'orm' => [
'mappings' => [
$this->bundle->getName() => [
'type' => 'yml',
'dir' => 'Resources/config/doctrine',
'prefix' => substr($ns, 0, strrpos($ns, '\\')) . '\\Domain',
],
]
]
]);
}
}
/**
* Configures JMS serializer to add mapping files if serializer config files are stored within this compact bundle in
* <Bundle>/Resources/config/serializer
*
* @param ContainerBuilder $container
*/
private function prependJmsSerializerConfiguration(ContainerBuilder $container)
{
$serializerConfigs = sprintf('%s/Resources/config/serializer', $this->bundle->getPath());
if (is_dir($serializerConfigs)) {
$ns = $this->bundle->getNamespace();
$container->prependExtensionConfig('jms_serializer', [
'metadata' => [
'directories' => [
$this->bundle->getName() => [
'namespace_prefix' => substr($ns, 0, strrpos($ns, '\\')),
'path' => sprintf('@%s/Resources/config/serializer', $this->bundle->getName()),
],
]
]
]);
}
}
}
<?php
namespace Fk\Core\Infrastructure\Bundle;
use Psr\Log\LoggerInterface;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\RouteCollection;
/**
* Automatically loads routing configuration in compact bundles to favor convention over configuration.
*
* Currently loads:
*
* - <Bundle>/Resources/config/routing.yml
*/
class CompactBundleRouteLoader extends Loader
{
/** @var KernelInterface required to access bundles */
private $kernel;
/** @var LoggerInterface */
private $log;
/** @var bool stores whether the routes have already been loaded */
private $loaded = false;
/**
* Provide access to kernel in order to load bundle routes.
*/
public function __construct(KernelInterface $kernel, LoggerInterface $log)
{
$this->kernel = $kernel;
$this->log = $log;
}
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
if (true === $this->loaded) {
throw new \RuntimeException('Do not add the "convention" loader twice');
}
$collection = new RouteCollection();
foreach ($this->kernel->getBundles() as $bundle) {
if (!$bundle instanceof CompactBundle) {
continue;
}
$routing = sprintf("%s/Resources/config/routing.yml", $bundle->getPath());
if (!file_exists($routing)) {
$this->log->debug('Compact bundle "{bundle}" has no routes defined.', [
'bundle' => $bundle->getName()
]);
continue;
}
$collection->addCollection($this->import($routing, 'yaml'));
$this->log->debug('Loaded routes for compact bundle "{bundle}".', [
'bundle' => $bundle->getName()
]);
}
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return 'convention' === $type;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment