diff --git a/src/Container/PackageProxyContainer.php b/src/Container/PackageProxyContainer.php index d5e3954..0434f53 100644 --- a/src/Container/PackageProxyContainer.php +++ b/src/Container/PackageProxyContainer.php @@ -66,7 +66,8 @@ private function tryContainer(): bool /** TODO: We need a better way to deal with status checking besides equality */ if ( - $this->package->statusIs(Package::STATUS_READY) + $this->package->statusIs(Package::STATUS_INITIALIZED) + || $this->package->statusIs(Package::STATUS_READY) || $this->package->statusIs(Package::STATUS_BOOTED) ) { $this->container = $this->package->container(); @@ -88,7 +89,6 @@ private function assertPackageBooted(string $id): void if ($this->tryContainer()) { return; } - $name = $this->package->name(); $status = $this->package->statusIs(Package::STATUS_FAILED) ? 'is errored' diff --git a/src/Package.php b/src/Package.php index 06177f2..bcea095 100644 --- a/src/Package.php +++ b/src/Package.php @@ -305,10 +305,14 @@ public function connect(Package $package): bool throw new \Exception($error, 0, $this->lastError); } - // Don't connect, if already booted or boot failed + // Don't connect, if status is failed $failed = $this->statusIs(self::STATUS_FAILED); - if ($failed || $this->statusIs(self::STATUS_BOOTED)) { - $status = $failed ? 'errored' : 'booted'; + // Don't allow connect if current package is already built + if ($failed + || $this->statusIs(self::STATUS_INITIALIZED) // built + || $this->statusIs(self::STATUS_BOOTED) // booted + ) { + $status = $failed ? 'errored' : 'built_or_booted'; $error = "{$errorMessage} to a {$status} package."; do_action( $this->hookName(self::ACTION_FAILED_CONNECTION), @@ -330,14 +334,15 @@ static function () use ($package): Properties { } ); - // If the other package is booted, we can obtain a container, otherwise + // If the other package is built or booted, we can obtain a container, otherwise // we build a proxy container $container = $package->statusIs(self::STATUS_BOOTED) + || $package->statusIs(self::STATUS_INITIALIZED) +// || $package->statusIs(self::STATUS_READY) ? $package->container() : new PackageProxyContainer($package); $this->containerConfigurator->addContainer($container); - do_action( $this->hookName(self::ACTION_PACKAGE_CONNECTED), $packageName, @@ -361,6 +366,9 @@ static function () use ($package): Properties { */ public function build(): Package { + if ($this->built) { + return $this; + } try { // Don't allow building the application multiple times. $this->assertStatus(self::STATUS_IDLE, 'build package'); diff --git a/tests/unit/PackageTest.php b/tests/unit/PackageTest.php index 3f810b5..f367217 100644 --- a/tests/unit/PackageTest.php +++ b/tests/unit/PackageTest.php @@ -35,6 +35,25 @@ public function testBasic(): void static::assertEmpty($package->modulesStatus()[Package::MODULES_ALL]); } + /** + * @test + */ + public function testBasicUsingBuild(): void + { + $expectedName = 'foo'; + $propertiesStub = $this->mockProperties($expectedName); + + $package = Package::new($propertiesStub); + + static::assertTrue($package->statusIs(Package::STATUS_IDLE)); + static::assertInstanceOf(Package::class, $package->build()); + static::assertTrue($package->statusIs(Package::STATUS_INITIALIZED)); + static::assertSame($expectedName, $package->name()); + static::assertInstanceOf(Properties::class, $package->properties()); + static::assertInstanceOf(ContainerInterface::class, $package->container()); + static::assertEmpty($package->modulesStatus()[Package::MODULES_ALL]); + } + /** * @param string $suffix * @param string $baseName @@ -105,6 +124,32 @@ public function testBootWithEmptyModule(): void static::assertFalse($package->boot()); } + /** + * @test + */ + public function testBuildWithEmptyModule(): void + { + $expectedId = 'my-module'; + + $moduleStub = $this->mockModule($expectedId); + $propertiesStub = $this->mockProperties('name', false); + + $package = Package::new($propertiesStub)->addModule($moduleStub); + + static::assertInstanceOf(Package::class, $package->build()); + static::assertTrue($package->moduleIs($expectedId, Package::MODULE_NOT_ADDED)); + static::assertFalse($package->moduleIs($expectedId, Package::MODULE_REGISTERED)); + static::assertFalse($package->moduleIs($expectedId, Package::MODULE_REGISTERED_FACTORIES)); + static::assertFalse($package->moduleIs($expectedId, Package::MODULE_EXTENDED)); + static::assertFalse($package->moduleIs($expectedId, Package::MODULE_ADDED)); + + // build again should keep the status of the package. + static::assertTrue($package->statusIs( Package::STATUS_INITIALIZED)); + $package->build(); + static::assertTrue($package->statusIs( Package::STATUS_INITIALIZED)); + + } + /** * @test */ @@ -127,6 +172,28 @@ public function testBootWithServiceModule(): void static::assertTrue($package->container()->has($serviceId)); } + /** + * @test + */ + public function testBootWithServiceModuleUsingBuild(): void + { + $moduleId = 'my-service-module'; + $serviceId = 'service-id'; + + $module = $this->mockModule($moduleId, ServiceModule::class); + $module->expects('services')->andReturn($this->stubServices($serviceId)); + + $package = Package::new($this->mockProperties())->addModule($module); + + static::assertInstanceOf(Package::class, $package->build()); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_REGISTERED)); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_REGISTERED_FACTORIES)); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_EXTENDED)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_ADDED)); + static::assertTrue($package->container()->has($serviceId)); + } + /** * @test */ @@ -149,6 +216,28 @@ public function testBootWithFactoryModule(): void static::assertTrue($package->container()->has($factoryId)); } + /** + * @test + */ + public function testBootWithFactoryModuleUsingBuild(): void + { + $moduleId = 'my-factory-module'; + $factoryId = 'factory-id'; + + $module = $this->mockModule($moduleId, FactoryModule::class); + $module->expects('factories')->andReturn($this->stubServices($factoryId)); + + $package = Package::new($this->mockProperties())->addModule($module); + + static::assertInstanceOf(Package::class, $package->build()); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_REGISTERED)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_REGISTERED_FACTORIES)); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_EXTENDED)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_ADDED)); + static::assertTrue($package->container()->has($factoryId)); + } + /** * @test */ @@ -172,6 +261,29 @@ public function testBootWithExtendingModuleWithNonExistingService(): void static::assertFalse($package->container()->has($extensionId)); } + /** + * @test + */ + public function testBuildWithExtendingModuleWithNonExistingService(): void + { + $moduleId = 'my-extension-module'; + $extensionId = 'extension-id'; + + $module = $this->mockModule($moduleId, ExtendingModule::class); + $module->expects('extensions')->andReturn($this->stubServices($extensionId)); + + $package = Package::new($this->mockProperties())->addModule($module); + + static::assertInstanceOf(Package::class, $package->build()); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_REGISTERED)); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_REGISTERED_FACTORIES)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_EXTENDED)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_ADDED)); + // false because extending a service not in container + static::assertFalse($package->container()->has($extensionId)); + } + /** * @test */ @@ -195,6 +307,29 @@ public function testBootWithExtendingModuleWithExistingService(): void static::assertTrue($package->container()->has($serviceId)); } + /** + * @test + */ + public function testBuildWithExtendingModuleWithExistingService(): void + { + $moduleId = 'my-extension-module'; + $serviceId = 'service-id'; + + $module = $this->mockModule($moduleId, ServiceModule::class, ExtendingModule::class); + $module->expects('services')->andReturn($this->stubServices($serviceId)); + $module->expects('extensions')->andReturn($this->stubServices($serviceId)); + + $package = Package::new($this->mockProperties())->addModule($module); + + static::assertInstanceOf(Package::class, $package->build()); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_NOT_ADDED)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_REGISTERED)); + static::assertFalse($package->moduleIs($moduleId, Package::MODULE_REGISTERED_FACTORIES)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_EXTENDED)); + static::assertTrue($package->moduleIs($moduleId, Package::MODULE_ADDED)); + static::assertTrue($package->container()->has($serviceId)); + } + /** * @test */ @@ -497,6 +632,38 @@ public function testPackageConnection(): void static::assertInstanceOf(\ArrayObject::class, $package2->container()->get('service_1')); } + /** + * Test we can connect services across packages. + * + * @test + */ + public function testPackageConnectionWhenOnePackageIsBuiltAndTheOtherDont(): void + { + $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1->expects('services')->andReturn($this->stubServices('service_1')); + $package1 = Package::new($this->mockProperties('package_1', false)) + ->addModule($module1); + + $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2->expects('services')->andReturn($this->stubServices('service_2')); + $package2 = Package::new($this->mockProperties('package_2', false)) + ->addModule($module2); + + Monkey\Actions\expectDone($package2->hookName(Package::ACTION_PACKAGE_CONNECTED)) + ->once() + ->with($package1->name(), Package::STATUS_IDLE, false); + + $package1->build(); + + $connected = $package2->connect($package1); + $package2->build(); + + static::assertTrue($connected); + static::assertSame(['package_1' => true], $package2->connectedPackages()); + // retrieve a Package 1's service from Package 2's container. + static::assertInstanceOf(\ArrayObject::class, $package2->container()->get('service_1')); + } + /** * Test we can not connect services when the package how call connect is booted. * @@ -535,6 +702,44 @@ function (\Throwable $throwable): void { static::assertSame(['package_1' => false], $package2->connectedPackages()); } + /** + * Test we can not connect services when the package how call connect is built. + * + * @test + */ + public function testPackageConnectionFailsIfBuiltWithDebugOff(): void + { + $module1 = $this->mockModule('module_1', ServiceModule::class); + $module1->expects('services')->andReturn($this->stubServices('service_1')); + $package1 = Package::new($this->mockProperties('package_1', false)) + ->addModule($module1); + + $module2 = $this->mockModule('module_2', ServiceModule::class); + $module2->expects('services')->andReturn($this->stubServices('service_2')); + $package2 = Package::new($this->mockProperties('package_2', false)) + ->addModule($module2); + + $package1->build(); + $package2->build(); + + Monkey\Actions\expectDone($package2->hookName(Package::ACTION_FAILED_CONNECTION)) + ->once() + ->with($package1->name(), \Mockery::type(\WP_Error::class)); + + Monkey\Actions\expectDone($package2->hookName(Package::ACTION_FAILED_BUILD)) + ->once() + ->whenHappen( + function (\Throwable $throwable): void { + $this->assertThrowableMessageMatches($throwable, 'failed connect.+?built'); + } + ); + + $connected = $package2->connect($package1); + + static::assertFalse($connected); + static::assertSame(['package_1' => false], $package2->connectedPackages()); + } + /** * Test we can not connect services when the package how call connect is booted. *