Skip to content

Commit 687b682

Browse files
committed
Added ModularModule
1 parent ad4f440 commit 687b682

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed

src/Modules/ModularModule.php

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<?php
2+
3+
namespace Dhii\Modular\Module\Modules;
4+
5+
use Dhii\Modular\Module\ExtensionInterface;
6+
use Dhii\Modular\Module\FactoryInterface;
7+
use Dhii\Modular\Module\ModularInterface;
8+
use Dhii\Modular\Module\ModuleInterface;
9+
use Psr\Container\ContainerInterface;
10+
11+
/**
12+
* An implementation of a module that is composed of other modules.
13+
*
14+
* Modules of this implementation will compile and cache the merged factories and extensions of their child modules,
15+
* which are then provided via the {@link ModuleInterface::getFactories()} and {@link ModuleInterface::getExtensions()}
16+
* methods. When modular modules are run, each child module is also run with the same container in the order they are
17+
* given during construction.
18+
*
19+
* This class may be freely extended and used as a base for modular applications.
20+
* The {@link ModularModule::getSelfFactories()} and {@link ModularModule::getSelfExtensions()} methods may be
21+
* overridden to specify factories and extensions at the top level of the composition.
22+
*
23+
* The list of child modules does not need to be associative. However, it may be helpful to associate each module with
24+
* a key to allow for easy referencing of specific modules, such as automating the prefixing of each module via the
25+
* {@link PrefixingModule} decorator, using each module's key as the prefix.
26+
*
27+
* Example usage:
28+
* ```
29+
* $app = new ModularModule([
30+
* 'log' => new LogModule(),
31+
* 'db' => new DbModule(),
32+
* 'cms' => new CmsModule(),
33+
* ]);
34+
*
35+
* $app->run($c);
36+
* ```
37+
*
38+
* @since [*next-version*]
39+
*/
40+
class ModularModule implements ModuleInterface, ModularInterface
41+
{
42+
/**
43+
* @since [*next-version*]
44+
*
45+
* @var ModuleInterface[]
46+
*/
47+
protected $modules;
48+
49+
/**
50+
* @since [*next-version*]
51+
*
52+
* @var FactoryInterface[]
53+
*/
54+
protected $factories;
55+
56+
/**
57+
* @since [*next-version*]
58+
*
59+
* @var ExtensionInterface[]
60+
*/
61+
protected $extensions;
62+
63+
/**
64+
* Constructor.
65+
*
66+
* @since [*next-version*]
67+
*
68+
* @param ModuleInterface[] $modules The modules.
69+
*/
70+
public function __construct(array $modules)
71+
{
72+
$this->modules = $modules;
73+
74+
$this->compileServices();
75+
}
76+
77+
/**
78+
* @inheritDoc
79+
*/
80+
public function getFactories() : array
81+
{
82+
return $this->factories;
83+
}
84+
85+
/**
86+
* @inheritDoc
87+
*/
88+
public function getExtensions() : array
89+
{
90+
return $this->extensions;
91+
}
92+
93+
/**
94+
* @inheritDoc
95+
*/
96+
public function run(ContainerInterface $c)
97+
{
98+
foreach ($this->modules as $module) {
99+
$module->run($c);
100+
}
101+
}
102+
103+
/**
104+
* Retrieves the list of modules.
105+
*
106+
* @since [*next-version*]
107+
*
108+
* @return ModuleInterface[]
109+
*/
110+
public function getModules() : array
111+
{
112+
return $this->modules;
113+
}
114+
115+
/**
116+
* Retrieves the modular module's own factories.
117+
*
118+
* @since [*next-version*]
119+
*
120+
* @return FactoryInterface[]
121+
*/
122+
protected function getSelfFactories() : array
123+
{
124+
return [];
125+
}
126+
127+
/**
128+
* Retrieves the modular module's own extensions.
129+
*
130+
* @since [*next-version*]
131+
*
132+
* @return ExtensionInterface[]
133+
*/
134+
protected function getSelfExtensions() : array
135+
{
136+
return [];
137+
}
138+
139+
/**
140+
* Compiles all of the module services.
141+
*
142+
* @since [*next-version*]
143+
*/
144+
protected function compileServices()
145+
{
146+
$this->factories = $this->getSelfFactories();
147+
$this->extensions = $this->getSelfExtensions();
148+
149+
foreach ($this->modules as $module) {
150+
$this->factories = array_merge($this->factories, $module->getFactories());
151+
$moduleExtensions = $module->getExtensions();
152+
153+
if (empty($this->extensions)) {
154+
$this->extensions = $moduleExtensions;
155+
continue;
156+
}
157+
158+
foreach ($moduleExtensions as $key => $extension) {
159+
if (!array_key_exists($key, $this->extensions)) {
160+
$this->extensions[$key] = $extension;
161+
continue;
162+
}
163+
164+
$prevExtension = $this->extensions[$key];
165+
$this->extensions[$key] = function (ContainerInterface $c, $prev) use ($prevExtension, $extension) {
166+
return $extension($c, $prevExtension($c, $prev));
167+
};
168+
}
169+
}
170+
}
171+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
namespace Dhii\Modular\Module\UnitTest\Module;
4+
5+
use Dhii\Modular\Module\ModularInterface;
6+
use Dhii\Modular\Module\ModuleInterface;
7+
use Dhii\Modular\Module\Modules\ModularModule as TestSubject;
8+
use PHPUnit\Framework\MockObject\MockObject;
9+
use PHPUnit\Framework\TestCase;
10+
use Psr\Container\ContainerInterface;
11+
use ReflectionException;
12+
13+
class ModularModuleTest extends TestCase
14+
{
15+
/**
16+
* @since [*next-version*]
17+
*/
18+
public function testConstruct()
19+
{
20+
$subject = new TestSubject([]);
21+
22+
static::assertInstanceOf(ModuleInterface::class, $subject);
23+
static::assertInstanceOf(ModularInterface::class, $subject);
24+
}
25+
26+
/**
27+
* @since [*next-version*]
28+
*
29+
* @throws ReflectionException
30+
*/
31+
public function testGetFactories()
32+
{
33+
{
34+
$key1 = uniqid('key1');
35+
$key2 = uniqid('key2');
36+
$key3 = uniqid('key3');
37+
38+
$fac1 = uniqid('fac1');
39+
$fac2 = uniqid('fac2');
40+
$fac3 = uniqid('fac3');
41+
$fac4 = uniqid('fac4');
42+
43+
$factories1 = [
44+
$key1 => $fac1,
45+
$key2 => $fac2,
46+
];
47+
48+
$factories2 = [
49+
$key2 => $fac4,
50+
$key3 => $fac3,
51+
];
52+
}
53+
{
54+
/** @var ModuleInterface|MockObject $module1 */
55+
/** @var ModuleInterface|MockObject $module2 */
56+
$module1 = $this->getMockForAbstractClass(ModuleInterface::class);
57+
$module2 = $this->getMockForAbstractClass(ModuleInterface::class);
58+
59+
$module1->expects(static::once())->method('getFactories')->willReturn($factories1);
60+
$module2->expects(static::once())->method('getFactories')->willReturn($factories2);
61+
}
62+
63+
$subject = new TestSubject([$module1, $module2]);
64+
$actual = $subject->getFactories();
65+
66+
static::assertSame($fac1, $actual[$key1]);
67+
static::assertSame($fac4, $actual[$key2]);
68+
static::assertSame($fac3, $actual[$key3]);
69+
}
70+
71+
/**
72+
* @since [*next-version*]
73+
*
74+
* @throws ReflectionException
75+
*/
76+
public function testGetExtensions()
77+
{
78+
{
79+
$key1 = uniqid('key1');
80+
$key2 = uniqid('key2');
81+
$key3 = uniqid('key3');
82+
83+
$ext1 = uniqid('ext1');
84+
$ext2 = uniqid('ext2');
85+
$ext3 = uniqid('ext4');
86+
$ext4 = uniqid('ext5');
87+
88+
$extensions1 = [
89+
$key1 => $ext1,
90+
$key2 => $ext2,
91+
];
92+
93+
$extensions2 = [
94+
$key2 => $ext4,
95+
$key3 => $ext3,
96+
];
97+
}
98+
{
99+
/** @var ModuleInterface|MockObject $module1 */
100+
/** @var ModuleInterface|MockObject $module2 */
101+
$module1 = $this->getMockForAbstractClass(ModuleInterface::class);
102+
$module2 = $this->getMockForAbstractClass(ModuleInterface::class);
103+
104+
$module1->expects(static::once())->method('getExtensions')->willReturn($extensions1);
105+
$module2->expects(static::once())->method('getExtensions')->willReturn($extensions2);
106+
}
107+
108+
$subject = new TestSubject([$module1, $module2]);
109+
$actual = $subject->getExtensions();
110+
111+
static::assertSame($ext1, $actual[$key1]);
112+
static::assertSame($ext3, $actual[$key3]);
113+
114+
// The combined extension is neither of the exising ones, but still callable
115+
static::assertNotSame($ext2, $actual[$key2]);
116+
static::assertNotSame($ext4, $actual[$key2]);
117+
static::assertIsCallable($actual[$key2]);
118+
}
119+
120+
/**
121+
* @since [*next-version*]
122+
*
123+
* @throws ReflectionException
124+
*/
125+
public function testRun()
126+
{
127+
{
128+
$c = $this->getMockForAbstractClass(ContainerInterface::class);
129+
}
130+
{
131+
/** @var ModuleInterface|MockObject $module1 */
132+
/** @var ModuleInterface|MockObject $module2 */
133+
$module1 = $this->getMockForAbstractClass(ModuleInterface::class);
134+
$module2 = $this->getMockForAbstractClass(ModuleInterface::class);
135+
136+
$module1->expects(static::once())->method('run')->with($c);
137+
$module2->expects(static::once())->method('run')->with($c);
138+
}
139+
140+
$subject = new TestSubject([$module1, $module2]);
141+
$subject->run($c);
142+
}
143+
}

0 commit comments

Comments
 (0)