From 0ffe94f019c686c15cde7fecdfefdce2f52229f4 Mon Sep 17 00:00:00 2001 From: Pieter Digipolis Date: Wed, 21 Feb 2018 15:19:56 +0100 Subject: [PATCH 001/104] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index dcc2ed3..1fb4d7d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "require": { "php": ">=7.1", "symfony/symfony": ">=3.4", - "digipolisgent/setting-bundle": "^1.0.0@alpha", + "digipolisgent/setting-bundle": "dev-develop, "doctrine/doctrine-bundle": "^1.6", "doctrine/orm": "^2.5", "symfony/swiftmailer-bundle": "^2.3", From fda7e9e2359a28f297c65836799d833371b59796 Mon Sep 17 00:00:00 2001 From: Pieter Digipolis Date: Wed, 21 Feb 2018 15:21:59 +0100 Subject: [PATCH 002/104] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1fb4d7d..6068847 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "require": { "php": ">=7.1", "symfony/symfony": ">=3.4", - "digipolisgent/setting-bundle": "dev-develop, + "digipolisgent/setting-bundle": "dev-develop", "doctrine/doctrine-bundle": "^1.6", "doctrine/orm": "^2.5", "symfony/swiftmailer-bundle": "^2.3", From 35e2fa2f01aefa8a761ea22afb9bc782b7b14c5a Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Wed, 21 Feb 2018 15:26:41 +0100 Subject: [PATCH 003/104] Template replacement added --- Entity/AbstractApplication.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 3f48f8e..3db88ea 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -189,6 +189,7 @@ public function getApplicationEnvironmentByEnvironmentName(string $name) public static function getTemplateReplacements(): array { return [ + 'name()' => 'getName()', 'nameCanonical()' => 'getNameCanonical()', 'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', ]; From a54fdfc350aecd60c92a5414524bc4393384c0e4 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Thu, 1 Mar 2018 16:20:48 +0100 Subject: [PATCH 004/104] Extra info to log added when finished --- EventListener/BuildEventListener.php | 1 + EventListener/DestroyEventListener.php | 1 + 2 files changed, 2 insertions(+) diff --git a/EventListener/BuildEventListener.php b/EventListener/BuildEventListener.php index 552e3ee..838ea48 100644 --- a/EventListener/BuildEventListener.php +++ b/EventListener/BuildEventListener.php @@ -42,6 +42,7 @@ public function onStart(BuildEvent $event) public function onEnd(BuildEvent $event) { + $this->taskLoggerService->addLine('Build completed'); $task = $event->getTask(); $task->setStatus(Task::STATUS_PROCESSED); $this->entityManager->persist($task); diff --git a/EventListener/DestroyEventListener.php b/EventListener/DestroyEventListener.php index 4cc6ced..56091aa 100644 --- a/EventListener/DestroyEventListener.php +++ b/EventListener/DestroyEventListener.php @@ -49,6 +49,7 @@ public function onStart(DestroyEvent $event) */ public function onEnd(DestroyEvent $event) { + $this->taskLoggerService->addLine('Destroy completed'); $task = $event->getTask(); $task->setStatus(Task::STATUS_PROCESSED); $this->entityManager->persist($task); From 8f5910f991f61310b2a163623a66b431d6a91572 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Fri, 9 Mar 2018 14:38:57 +0100 Subject: [PATCH 005/104] Removed recursive validation --- Entity/AbstractApplication.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 3db88ea..c8760cc 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -54,8 +54,6 @@ abstract class AbstractApplication implements TemplateInterface * @var ArrayCollection * * @ORM\OneToMany(targetEntity="ApplicationEnvironment", mappedBy="application", cascade={"all"},fetch="EAGER") - * @Assert\Valid() - * @Assert\NotNull() */ protected $applicationEnvironments; From 704724b9b1f5c87b79b5b80f8dd652bb937c75d8 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Mon, 12 Mar 2018 10:41:57 +0100 Subject: [PATCH 006/104] Unit test fix --- Tests/Entity/AbstractApplicationTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Entity/AbstractApplicationTest.php b/Tests/Entity/AbstractApplicationTest.php index 4491da3..4a06401 100644 --- a/Tests/Entity/AbstractApplicationTest.php +++ b/Tests/Entity/AbstractApplicationTest.php @@ -15,6 +15,7 @@ class AbstractApplicationTest extends TestCase public function testGetTemplateReplacements() { $expected = [ + 'name()' => 'getName()', 'nameCanonical()' => 'getNameCanonical()', 'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', ]; From e2180dc99571b54bbe5a6e6ac671c6b3d6db8600 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 12 Mar 2018 16:32:41 +0100 Subject: [PATCH 007/104] Added template values - Application: Git repo - Application environment: Worker IP --- Entity/AbstractApplication.php | 1 + Entity/ApplicationEnvironment.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 3db88ea..e8a6184 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -191,6 +191,7 @@ public static function getTemplateReplacements(): array return [ 'name()' => 'getName()', 'nameCanonical()' => 'getNameCanonical()', + 'gitRepo()' => 'getGitRepo()', 'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', ]; } diff --git a/Entity/ApplicationEnvironment.php b/Entity/ApplicationEnvironment.php index 38bfe91..acf225e 100644 --- a/Entity/ApplicationEnvironment.php +++ b/Entity/ApplicationEnvironment.php @@ -97,6 +97,7 @@ public static function getTemplateReplacements(): array { return [ 'serverIps()' => 'getServerIps()', + 'workerServerIp()' => 'getWorkerServerIp()', 'environmentName()' => 'getEnvironment().getName()', 'config(key)' => 'getConfig(key)', 'databaseName()' => 'getDatabaseName()', @@ -248,6 +249,22 @@ public function getServerIps(): string return implode(' ', $serverIps); } + /** + * @return string + */ + public function getWorkerServerIp(): string + { + /** @var VirtualServer $server */ + $servers = $this->getEnvironment()->getVirtualServers(); + foreach ($servers as $server) { + if ($server->isTaskServer()) { + return $server->getHost(); + } + } + + return end($servers)->getHost(); + } + public function getConfig($key) { foreach ($this->getSettingDataValues() as $settingDataValue) { From be42a033b3313cc53730bcd144a8a3cfcf83858c Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 12 Mar 2018 16:41:09 +0100 Subject: [PATCH 008/104] Added domain template. --- Entity/ApplicationEnvironment.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Entity/ApplicationEnvironment.php b/Entity/ApplicationEnvironment.php index acf225e..b679d3e 100644 --- a/Entity/ApplicationEnvironment.php +++ b/Entity/ApplicationEnvironment.php @@ -104,6 +104,7 @@ public static function getTemplateReplacements(): array 'databaseUser()' => 'getDatabaseUser()', 'databasePassword()' => 'getDatabasePassword()', 'gitRef()' => 'getGitRef()', + 'domain()' => 'getDomain()', ]; } From 5250bdef14ce8e1895da765f3705cbae0ba8bafc Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 13 Mar 2018 09:30:35 +0100 Subject: [PATCH 009/104] Fix test failures. --- Tests/Entity/AbstractApplicationTest.php | 3 ++- Tests/Entity/ApplicationEnvironmentTest.php | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/Entity/AbstractApplicationTest.php b/Tests/Entity/AbstractApplicationTest.php index 4a06401..c3ca999 100644 --- a/Tests/Entity/AbstractApplicationTest.php +++ b/Tests/Entity/AbstractApplicationTest.php @@ -18,6 +18,7 @@ public function testGetTemplateReplacements() 'name()' => 'getName()', 'nameCanonical()' => 'getNameCanonical()', 'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', + 'gitRepo()' => 'getGitRepo()', ]; $this->assertEquals($expected, QuuxApplication::getTemplateReplacements()); @@ -65,7 +66,7 @@ public function testGettersAndSetters() $this->assertCount(0,$application->getApplicationEnvironments()); $this->assertNull($application->getId()); - + $application->setDeleted(true); $this->assertTrue($application->isDeleted()); } diff --git a/Tests/Entity/ApplicationEnvironmentTest.php b/Tests/Entity/ApplicationEnvironmentTest.php index e1e0545..7d023ab 100644 --- a/Tests/Entity/ApplicationEnvironmentTest.php +++ b/Tests/Entity/ApplicationEnvironmentTest.php @@ -23,12 +23,14 @@ public function testTemplateReplacements() { $expected = [ 'serverIps()' => 'getServerIps()', + 'workerServerIp()' => 'getWorkerServerIp()', 'environmentName()' => 'getEnvironment().getName()', 'config(key)' => 'getConfig(key)', 'databaseName()' => 'getDatabaseName()', 'databaseUser()' => 'getDatabaseUser()', 'databasePassword()' => 'getDatabasePassword()', 'gitRef()' => 'getGitRef()', + 'domain()' => 'getDomain()', ]; $this->assertEquals($expected, ApplicationEnvironment::getTemplateReplacements()); From 2ea3d2a73c91981c00f89c78fea0b44bf0c3e667 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Tue, 13 Mar 2018 10:49:14 +0100 Subject: [PATCH 010/104] WEBDOM-314: default settings not showing up --- EventListener/EnvironmentEventListener.php | 11 --------- .../EnvironmentEventListenerTest.php | 23 +++---------------- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/EventListener/EnvironmentEventListener.php b/EventListener/EnvironmentEventListener.php index dc723e3..63f94a8 100644 --- a/EventListener/EnvironmentEventListener.php +++ b/EventListener/EnvironmentEventListener.php @@ -26,17 +26,6 @@ public function postPersist(LifecycleEventArgs $args) $entityManager = $args->getEntityManager(); if ($entity instanceof Environment) { - $applications = $entityManager->getRepository(AbstractApplication::class)->findAll(); - - foreach ($applications as $application) { - $applicationEnvironment = new ApplicationEnvironment(); - $applicationEnvironment->setApplication($application); - $applicationEnvironment->setEnvironment($entity); - - $entityManager->persist($applicationEnvironment); - $entityManager->flush(); - } - $applicationTypes = $entityManager->getRepository(ApplicationType::class)->findAll(); foreach ($applicationTypes as $applicationType) { diff --git a/Tests/EventListener/EnvironmentEventListenerTest.php b/Tests/EventListener/EnvironmentEventListenerTest.php index 85a3bce..5ab49f0 100644 --- a/Tests/EventListener/EnvironmentEventListenerTest.php +++ b/Tests/EventListener/EnvironmentEventListenerTest.php @@ -39,28 +39,11 @@ public function testPostPersist() $applications = new ArrayCollection(); $applications->add(new QuuxApplication()); - $entityManager - ->expects($this->at(0)) - ->method('getRepository') - ->with($this->equalTo(AbstractApplication::class)) - ->willReturn( - $this->getRepositoryMock($applications) - ); - - $entityManager - ->expects($this->at(1)) - ->method('persist'); - - - $entityManager - ->expects($this->at(2)) - ->method('flush'); - $applicationTypes = new ArrayCollection(); $applicationTypes->add(new ApplicationType()); $entityManager - ->expects($this->at(3)) + ->expects($this->at(0)) ->method('getRepository') ->with($this->equalTo(ApplicationType::class)) ->willReturn( @@ -68,12 +51,12 @@ public function testPostPersist() ); $entityManager - ->expects($this->at(4)) + ->expects($this->at(1)) ->method('persist'); $entityManager - ->expects($this->at(5)) + ->expects($this->at(2)) ->method('flush'); $args = $this->getLifecycleEventArgsMock($entity, $entityManager); From 6e9018d49f5b54911407f69cccb7759b3695a01c Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Thu, 15 Mar 2018 16:29:56 +0100 Subject: [PATCH 011/104] WEBDOM-306: Mark build as failed when an error occurs --- Entity/Repository/TaskRepository.php | 3 ++- Entity/Task.php | 1 + EventListener/BuildEventListener.php | 5 +++++ Service/TaskLoggerService.php | 7 +++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Entity/Repository/TaskRepository.php b/Entity/Repository/TaskRepository.php index d042f6b..ec7c483 100644 --- a/Entity/Repository/TaskRepository.php +++ b/Entity/Repository/TaskRepository.php @@ -28,6 +28,7 @@ public function getNextTask($type) public function getLastTaskId(ApplicationEnvironment $applicationEnvironment, string $type) { + $task = $this->_em->createQueryBuilder() ->select('t') ->from(Task::class, 't') @@ -36,7 +37,7 @@ public function getLastTaskId(ApplicationEnvironment $applicationEnvironment, st ->andWhere('ae.id=:id') ->setParameter('type', $type) ->setParameter('id', $applicationEnvironment->getId()) - ->orderBy('t.created') + ->orderBy('t.created','DESC') ->setMaxResults(1) ->getQuery() ->getOneOrNullResult(); diff --git a/Entity/Task.php b/Entity/Task.php index 3f79143..b3c4a0b 100644 --- a/Entity/Task.php +++ b/Entity/Task.php @@ -16,6 +16,7 @@ class Task const STATUS_NEW = 'new'; const STATUS_IN_PROGRESS = 'in_progress'; const STATUS_PROCESSED = 'processed'; + const STATUS_FAILED = 'failed'; const TYPE_BUILD = 'build'; const TYPE_DESTROY = 'destroy'; diff --git a/EventListener/BuildEventListener.php b/EventListener/BuildEventListener.php index 838ea48..543e384 100644 --- a/EventListener/BuildEventListener.php +++ b/EventListener/BuildEventListener.php @@ -42,6 +42,11 @@ public function onStart(BuildEvent $event) public function onEnd(BuildEvent $event) { + if ($event->getTask()->getStatus() == Task::STATUS_FAILED) { + $this->taskLoggerService->addLine('Build failed'); + return; + } + $this->taskLoggerService->addLine('Build completed'); $task = $event->getTask(); $task->setStatus(Task::STATUS_PROCESSED); diff --git a/Service/TaskLoggerService.php b/Service/TaskLoggerService.php index 448ad75..cbae01f 100644 --- a/Service/TaskLoggerService.php +++ b/Service/TaskLoggerService.php @@ -45,4 +45,11 @@ public function addLine(string $line) $this->entityManager->persist($this->task); $this->entityManager->flush(); } + + public function endTask() + { + $this->task->setStatus(Task::STATUS_FAILED); + $this->entityManager->persist($this->task); + $this->entityManager->flush(); + } } From aa80f23cb7737977feff6a8715aa7d477aa45e65 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Fri, 16 Mar 2018 09:28:04 +0100 Subject: [PATCH 012/104] WEBDOM-320: add gitref field to environment --- Entity/Environment.php | 24 ++++++++++++++++++++++++ Form/Type/EnvironmentFormType.php | 1 + 2 files changed, 25 insertions(+) diff --git a/Entity/Environment.php b/Entity/Environment.php index ac46971..4aa049d 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -60,6 +60,14 @@ class Environment */ protected $virtualServers; + /** + * @var string + * + * @ORM\Column(name="git_ref",type="string",nullable=true) + * @Assert\NotBlank() + */ + protected $gitRef; + /** * Creates a new environment. */ @@ -178,4 +186,20 @@ public function getVirtualServers() { return $this->virtualServers; } + + /** + * @return string + */ + public function getGitRef(): ?cstring + { + return $this->gitRef; + } + + /** + * @param string $gitRef + */ + public function setGitRef(string $gitRef) + { + $this->gitRef = $gitRef; + } } diff --git a/Form/Type/EnvironmentFormType.php b/Form/Type/EnvironmentFormType.php index f1f9873..33c695f 100644 --- a/Form/Type/EnvironmentFormType.php +++ b/Form/Type/EnvironmentFormType.php @@ -37,6 +37,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) parent::buildForm($builder, $options); $builder->add('name'); $builder->add('prod'); + $builder->add('gitRef'); $builder->addEventSubscriber(new SettingFormListener($this->formService)); } From 881c8254e1366b529014cb77b8c888c2b9f22298 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Fri, 16 Mar 2018 10:09:35 +0100 Subject: [PATCH 013/104] Return type fix --- Entity/Environment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entity/Environment.php b/Entity/Environment.php index 4aa049d..d5d5c90 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -190,7 +190,7 @@ public function getVirtualServers() /** * @return string */ - public function getGitRef(): ?cstring + public function getGitRef(): string { return $this->gitRef; } From 4e5953d7c2eb92393ee50fea687880d79cb6c475 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Fri, 16 Mar 2018 09:28:04 +0100 Subject: [PATCH 014/104] WEBDOM-320: add gitref field to environment --- Entity/Environment.php | 24 ++++++++++++++++++++++++ Form/Type/EnvironmentFormType.php | 1 + 2 files changed, 25 insertions(+) diff --git a/Entity/Environment.php b/Entity/Environment.php index ac46971..566a8aa 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -60,6 +60,14 @@ class Environment */ protected $virtualServers; + /** + * @var string + * + * @ORM\Column(name="git_ref",type="string",nullable=true) + * @Assert\NotBlank() + */ + protected $gitRef; + /** * Creates a new environment. */ @@ -178,4 +186,20 @@ public function getVirtualServers() { return $this->virtualServers; } + + /** + * @return string + */ + public function getGitRef(): ?string + { + return $this->gitRef; + } + + /** + * @param string $gitRef + */ + public function setGitRef(string $gitRef) + { + $this->gitRef = $gitRef; + } } diff --git a/Form/Type/EnvironmentFormType.php b/Form/Type/EnvironmentFormType.php index f1f9873..33c695f 100644 --- a/Form/Type/EnvironmentFormType.php +++ b/Form/Type/EnvironmentFormType.php @@ -37,6 +37,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) parent::buildForm($builder, $options); $builder->add('name'); $builder->add('prod'); + $builder->add('gitRef'); $builder->addEventSubscriber(new SettingFormListener($this->formService)); } From 2ad05faa543b9c6577563f6413c0e1fe5853fd30 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Thu, 22 Mar 2018 14:18:02 +0100 Subject: [PATCH 015/104] WEBDOM-329: add templating for environment --- Entity/Environment.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Entity/Environment.php b/Entity/Environment.php index 566a8aa..c1ec0f6 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -14,7 +14,7 @@ * @ORM\Table(name="environment") * @UniqueEntity(fields={"name"}) */ -class Environment +class Environment implements TemplateInterface { use SettingImplementationTrait; @@ -202,4 +202,26 @@ public function setGitRef(string $gitRef) { $this->gitRef = $gitRef; } + + public function getConfig($key) + { + foreach ($this->getSettingDataValues() as $settingDataValue) { + if ($settingDataValue->getSettingDataType()->getKey() == $key) { + return $settingDataValue->getValue(); + } + } + + return ''; + } + + /** + * @return array + */ + public static function getTemplateReplacements(): array + { + return [ + 'config(key)' => 'getConfig(key)', + 'gitRef()' => 'getGitRef()', + ]; + } } From bda07976ecdc8c967d49f5f35af3057f001f439e Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 27 Mar 2018 16:50:43 +0200 Subject: [PATCH 016/104] Fix replacements in templates with arguments. --- Service/TemplateService.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Service/TemplateService.php b/Service/TemplateService.php index e3410d9..55840c9 100644 --- a/Service/TemplateService.php +++ b/Service/TemplateService.php @@ -41,6 +41,7 @@ public function replaceKeys($text, array $entities = array()): string // Complete the pattern and escape all existing special characters $pattern = '[[ ' . $entityPrefix . ':' . $templateReplacementKey . ' ]]'; $pattern = str_replace(['(', ')', '[', ']'], ['\(', '\)', '\[', '\]'], $pattern); + $replacePattern = $pattern; // Get all the arguments out of the pattern so we can match them with the real arguments foreach ($replacementArguments as $replacementArgument) { @@ -60,6 +61,7 @@ public function replaceKeys($text, array $entities = array()): string // Get a key value pair of all arguments foreach ($replacementArguments as $key => $value) { $replacementArguments[$value] = $matches[$key + 1]; + $replacePattern = str_replace($replacementArguments[$key], $matches[$key + 1 ], $replacePattern); } // Get all functions that should be executed @@ -83,7 +85,7 @@ public function replaceKeys($text, array $entities = array()): string } // Replace the pattern with the found value - $text = preg_replace('/' . $pattern . '/', $passingValue, $text); + $text = preg_replace('/' . $replacePattern . '/', $passingValue, $text); } } } From 00453411e52a5f8efce5276b4a8e04aec81943a1 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 9 Apr 2018 12:54:56 +0200 Subject: [PATCH 017/104] Canonical name should be 14 characters. --- Entity/AbstractApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 1dd22e2..9b39f2d 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -106,7 +106,7 @@ public function setName(string $name) public function getNameCanonical() { $name = strtolower(preg_replace("/[^a-zA-Z0-9]+/", "", $this->getName())); - return substr($name, 0, 12); + return substr($name, 0, 14); } /** From 409bebf035215f94caa79347b8faf8fe9cf8b7a2 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 9 Apr 2018 15:10:16 +0200 Subject: [PATCH 018/104] Fix tests for canonical name of 14 characters. --- Tests/Entity/AbstractApplicationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Entity/AbstractApplicationTest.php b/Tests/Entity/AbstractApplicationTest.php index c3ca999..ae25803 100644 --- a/Tests/Entity/AbstractApplicationTest.php +++ b/Tests/Entity/AbstractApplicationTest.php @@ -48,7 +48,7 @@ public function testGettersAndSetters() $application->setName('My application name'); $this->assertEquals('My application name', $application->getName()); - $this->assertEquals('myapplicatio', $application->getNameCanonical()); + $this->assertEquals('myapplicationn', $application->getNameCanonical()); $this->assertTrue($application->isHasDatabase()); $application->setHasDatabase(false); From 7bc952df71b956ce63a76572bf8ed602e4183a73 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 13 Apr 2018 16:19:50 +0200 Subject: [PATCH 019/104] Fixed loading application environments. --- Entity/ApplicationTypeEnvironment.php | 4 ++-- .../ApplicationTypeEnvironmentRepository.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 Repository/ApplicationTypeEnvironmentRepository.php diff --git a/Entity/ApplicationTypeEnvironment.php b/Entity/ApplicationTypeEnvironment.php index c76746e..69564d1 100644 --- a/Entity/ApplicationTypeEnvironment.php +++ b/Entity/ApplicationTypeEnvironment.php @@ -11,7 +11,7 @@ * Class ApplicationTypeEnvironment * @package DigipolisGent\Domainator9k\CoreBundle\Entity * - * @ORM\Entity() + * @ORM\Entity(repositoryClass="DigipolisGent\Domainator9k\CoreBundle\Repository\ApplicationTypeEnvironmentRepository") */ class ApplicationTypeEnvironment { @@ -79,4 +79,4 @@ public function getEnvironmentName() { return $this->getEnvironment()->getName(); } -} \ No newline at end of file +} diff --git a/Repository/ApplicationTypeEnvironmentRepository.php b/Repository/ApplicationTypeEnvironmentRepository.php new file mode 100644 index 0000000..7f99de1 --- /dev/null +++ b/Repository/ApplicationTypeEnvironmentRepository.php @@ -0,0 +1,17 @@ +createQueryBuilder('ate') + ->innerJoin('ate.applicationType', 'at', Query\Expr\Join::ON) + ->andWhere('at.name = :name') + ->setParameter('name', $type) + ->getQuery() + ->execute(); + } +} From c16b560d6d82608d5113a951d1454e6df8223903 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Mon, 16 Apr 2018 10:20:34 +0200 Subject: [PATCH 020/104] Fixed application type join. --- Repository/ApplicationTypeEnvironmentRepository.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Repository/ApplicationTypeEnvironmentRepository.php b/Repository/ApplicationTypeEnvironmentRepository.php index 7f99de1..e9b757b 100644 --- a/Repository/ApplicationTypeEnvironmentRepository.php +++ b/Repository/ApplicationTypeEnvironmentRepository.php @@ -6,12 +6,21 @@ class ApplicationTypeEnvironmentRepository extends EntityRepository { + /** + * Get all environments of the specified application type. + * + * @param $type + * The application type name. + * + * @return array + * The query results. + */ public function findAllByApplicationType($type) { return $this->createQueryBuilder('ate') - ->innerJoin('ate.applicationType', 'at', Query\Expr\Join::ON) + ->innerJoin('ate.applicationType', 'at') ->andWhere('at.name = :name') ->setParameter('name', $type) ->getQuery() - ->execute(); + ->getResult(); } } From 6f740b68407375e9128758eab7d8fd5a8d18853a Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Wed, 18 Apr 2018 10:46:25 +0200 Subject: [PATCH 021/104] WEBDOM-343: sort on application type --- Entity/AbstractApplication.php | 17 ++++++++++++++++- Form/Type/AbstractApplicationFormType.php | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 9b39f2d..c2498b6 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -18,7 +18,8 @@ * @ORM\Table() * @ORM\InheritanceType("JOINED") * @ORM\DiscriminatorColumn(name="discr",type="string") - * @UniqueEntity(fields={"name"}) + * @UniqueEntity(fields={"name"})] + * @ORM\HasLifecycleCallbacks() */ abstract class AbstractApplication implements TemplateInterface { @@ -64,6 +65,13 @@ abstract class AbstractApplication implements TemplateInterface */ protected $deleted = false; + /** + * @var string + * + * @ORM\Column(name="application_type",type="string") + */ + protected $applicationType; + /** * @return string */ @@ -209,4 +217,11 @@ public function setDeleted(bool $deleted = false) { $this->deleted = $deleted; } + + /** + * @ORM\PrePersist() + */ + public function prePersist(){ + $this->applicationType = $this::getApplicationType(); + } } diff --git a/Form/Type/AbstractApplicationFormType.php b/Form/Type/AbstractApplicationFormType.php index 871d7a5..09cdf99 100644 --- a/Form/Type/AbstractApplicationFormType.php +++ b/Form/Type/AbstractApplicationFormType.php @@ -9,6 +9,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; /** From 9b659fc16acb50c7a83e81a90f05b755bf3edde8 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Wed, 18 Apr 2018 12:48:51 +0200 Subject: [PATCH 022/104] Cancel status added --- Entity/Task.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Entity/Task.php b/Entity/Task.php index b3c4a0b..2628717 100644 --- a/Entity/Task.php +++ b/Entity/Task.php @@ -17,6 +17,7 @@ class Task const STATUS_IN_PROGRESS = 'in_progress'; const STATUS_PROCESSED = 'processed'; const STATUS_FAILED = 'failed'; + const STATUS_CANCEL= 'cancel'; const TYPE_BUILD = 'build'; const TYPE_DESTROY = 'destroy'; From 09893a39ecbcb4e46514bef47ee8289e6c51ff28 Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Wed, 25 Apr 2018 16:14:30 +0200 Subject: [PATCH 023/104] WEBDOM-368: add priority to environment --- Entity/Environment.php | 23 +++++++++++++++++++++++ Form/Type/EnvironmentFormType.php | 1 + 2 files changed, 24 insertions(+) diff --git a/Entity/Environment.php b/Entity/Environment.php index c1ec0f6..769dca7 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -68,6 +68,13 @@ class Environment implements TemplateInterface */ protected $gitRef; + /** + * @var integer + * + * @ORM\Column(name="priority",type="integer",nullable=true) + */ + protected $priority; + /** * Creates a new environment. */ @@ -224,4 +231,20 @@ public static function getTemplateReplacements(): array 'gitRef()' => 'getGitRef()', ]; } + + /** + * @return int + */ + public function getPriority(): ?int + { + return $this->priority; + } + + /** + * @param int $priority + */ + public function setPriority(int $priority = null) + { + $this->priority = $priority; + } } diff --git a/Form/Type/EnvironmentFormType.php b/Form/Type/EnvironmentFormType.php index 33c695f..bc8d947 100644 --- a/Form/Type/EnvironmentFormType.php +++ b/Form/Type/EnvironmentFormType.php @@ -38,6 +38,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->add('name'); $builder->add('prod'); $builder->add('gitRef'); + $builder->add('priority'); $builder->addEventSubscriber(new SettingFormListener($this->formService)); } From 05d10815232f5f68a553246ffe26de599ceaa77c Mon Sep 17 00:00:00 2001 From: Pieter MAssoels Date: Fri, 27 Apr 2018 15:25:29 +0200 Subject: [PATCH 024/104] table rename --- Entity/AbstractApplication.php | 2 +- Entity/ApplicationType.php | 1 + Entity/ApplicationTypeEnvironment.php | 1 + Entity/VirtualServer.php | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index c2498b6..125f955 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -15,7 +15,7 @@ * @package DigipolisGent\Domainator9k\CoreBundle\Entity * * @ORM\Entity() - * @ORM\Table() + * @ORM\Table(name="abstract_application") * @ORM\InheritanceType("JOINED") * @ORM\DiscriminatorColumn(name="discr",type="string") * @UniqueEntity(fields={"name"})] diff --git a/Entity/ApplicationType.php b/Entity/ApplicationType.php index a0e3a27..1255cf4 100644 --- a/Entity/ApplicationType.php +++ b/Entity/ApplicationType.php @@ -13,6 +13,7 @@ * @package DigipolisGent\Domainator9k\CoreBundle\Entity * * @ORM\Entity() + * @ORM\Table(name="application_type") */ class ApplicationType { diff --git a/Entity/ApplicationTypeEnvironment.php b/Entity/ApplicationTypeEnvironment.php index 69564d1..f7319fc 100644 --- a/Entity/ApplicationTypeEnvironment.php +++ b/Entity/ApplicationTypeEnvironment.php @@ -12,6 +12,7 @@ * @package DigipolisGent\Domainator9k\CoreBundle\Entity * * @ORM\Entity(repositoryClass="DigipolisGent\Domainator9k\CoreBundle\Repository\ApplicationTypeEnvironmentRepository") + * @ORM\Table(name="application_type_environment") */ class ApplicationTypeEnvironment { diff --git a/Entity/VirtualServer.php b/Entity/VirtualServer.php index 1f36177..71c8f87 100644 --- a/Entity/VirtualServer.php +++ b/Entity/VirtualServer.php @@ -9,7 +9,7 @@ /** * @ORM\Entity - * @ORM\Table(name="virtualserver") + * @ORM\Table(name="virtual_server") */ class VirtualServer { From af84721722800286b3779db7d22df8cb2434b3de Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 12:20:15 +0200 Subject: [PATCH 025/104] WEBDOM-357: Added token entity for generic replacements. --- Entity/Token.php | 83 +++++++++++++++++++++++++++++++++++ Form/Type/TokenFormType.php | 47 ++++++++++++++++++++ Resources/config/services.yml | 5 ++- Service/TokenService.php | 73 ++++++++++++++++++++++++++++++ composer.json | 3 +- 5 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 Entity/Token.php create mode 100644 Form/Type/TokenFormType.php create mode 100644 Service/TokenService.php diff --git a/Entity/Token.php b/Entity/Token.php new file mode 100644 index 0000000..77af678 --- /dev/null +++ b/Entity/Token.php @@ -0,0 +1,83 @@ +name; + } + + /** + * Sets the name. + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Gets the value. + * + * @return string + */ + public function getValue(): ?string + { + return $this->value; + } + + /** + * Sets the value. + * + * @param string $value + * + * @return $this + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } +} diff --git a/Form/Type/TokenFormType.php b/Form/Type/TokenFormType.php new file mode 100644 index 0000000..65a35ed --- /dev/null +++ b/Form/Type/TokenFormType.php @@ -0,0 +1,47 @@ +formService = $formService; + } + + /** + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + parent::buildForm($builder, $options); + $builder->add('name'); + $builder->add('value'); + $builder->addEventSubscriber(new SettingFormListener($this->formService)); + } + + /** + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + $resolver->setDefault('data_class', Token::class); + } +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 7a7c241..8382d1c 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -15,11 +15,14 @@ services: - { name: kernel.event_listener, event: domainator.destroy, method: onStart, priority: 99 } - { name: kernel.event_listener, event: domainator.destroy, method: onEnd, priority: 1 } DigipolisGent\Domainator9k\CoreBundle\Service\TemplateService: + DigipolisGent\Domainator9k\CoreBundle\Service\TokenService: DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationEnvironmentFormType: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\EnvironmentFormType: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\VirtualServerFormType: tags: [form.type] + DigipolisGent\Domainator9k\CoreBundle\Form\Type\TokenFormType: + tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationTypeEnvironmentFormType: - tags: [form.type] \ No newline at end of file + tags: [form.type] diff --git a/Service/TokenService.php b/Service/TokenService.php new file mode 100644 index 0000000..5a33549 --- /dev/null +++ b/Service/TokenService.php @@ -0,0 +1,73 @@ +repository = $entityManager->getRepository(Token::class); + $this->studlyCapsCaseTransformer = new CaseTransformer(new SnakeCase(), new StudlyCaps()); + $this->snakeCaseTransformer = new CaseTransformer(new StudlyCaps(), new SnakeCase()); + } + + public static function getTemplateReplacements(): array + { + $tokens = $this->repository->findAll(); + $replacements = []; + foreach ($tokens as $token) { + $replacements[$token->getName()] = 'get' . $this->studlyCapsCaseTransformer->transform($token->getName()) . '()'; + } + + return $replacements; + } + + public function __call(string $name, array $arguments) + { + if (strpos($name, 'get') !== 0) { + throw new \BadMethodCallException('Call to undefined method ' . static::class . '::' . $name); + } + + $replacements = static::getTemplateReplacements(); + $tokenName = array_search($name . '()', $replacements); + if ($tokenName === false) { + throw new \BadMethodCallException('Call to undefined method ' . static::class . '::' . $name); + } + + $token = $this->repository->findOneBy(['name' => $tokenName]); + if (!$token) { + throw new \BadMethodCallException('Token ' . $tokenName . ' not found'); + } + + return $token->getValue(); + } +} diff --git a/composer.json b/composer.json index 6068847..1fed448 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "symfony/swiftmailer-bundle": "^2.3", "phpseclib/phpseclib": "^2.0", "webmozart/path-util": "^2.3", - "doctrine/doctrine-fixtures-bundle": "^2.3" + "doctrine/doctrine-fixtures-bundle": "^2.3", + "mattketmo/camel": "^1.1" }, "require-dev": { "phpunit/phpunit": "6.5" From d93001b9c2e53a4826923e8cdffa4c97c4db5aaf Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 12:24:19 +0200 Subject: [PATCH 026/104] WEBDOM-357: Code style fixes. --- Service/TokenService.php | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Service/TokenService.php b/Service/TokenService.php index 5a33549..1b6980b 100644 --- a/Service/TokenService.php +++ b/Service/TokenService.php @@ -11,7 +11,6 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; - /** * Class TokenService * @package DigipolisGent\Domainator9k\CoreBundle\Service @@ -27,17 +26,11 @@ class TokenService implements TemplateInterface /** * @var CaseTransformer */ - protected $studlyCapsCaseTransformer; - - /** - * @var CaseTransformer - */ - protected $snakeCaseTransformer; + protected $caseTransformer; public function __construct(EntityManager $entityManager) { $this->repository = $entityManager->getRepository(Token::class); - $this->studlyCapsCaseTransformer = new CaseTransformer(new SnakeCase(), new StudlyCaps()); - $this->snakeCaseTransformer = new CaseTransformer(new StudlyCaps(), new SnakeCase()); + $this->caseTransformer = new CaseTransformer(new SnakeCase(), new StudlyCaps()); } public static function getTemplateReplacements(): array @@ -45,7 +38,7 @@ public static function getTemplateReplacements(): array $tokens = $this->repository->findAll(); $replacements = []; foreach ($tokens as $token) { - $replacements[$token->getName()] = 'get' . $this->studlyCapsCaseTransformer->transform($token->getName()) . '()'; + $replacements[$token->getName()] = 'get' . $this->caseTransformer->transform($token->getName()) . '()'; } return $replacements; From 60387509f6c88951549b3bbd97aced0e7d4a6db9 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 12:25:37 +0200 Subject: [PATCH 027/104] WEBDOM-357: Code style fixes. --- Service/TokenService.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Service/TokenService.php b/Service/TokenService.php index 1b6980b..629f8d9 100644 --- a/Service/TokenService.php +++ b/Service/TokenService.php @@ -28,7 +28,8 @@ class TokenService implements TemplateInterface */ protected $caseTransformer; - public function __construct(EntityManager $entityManager) { + public function __construct(EntityManager $entityManager) + { $this->repository = $entityManager->getRepository(Token::class); $this->caseTransformer = new CaseTransformer(new SnakeCase(), new StudlyCaps()); } From 675b3718ccd02943b1f5bf6942ca38eab4c11c4a Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 15:02:23 +0200 Subject: [PATCH 028/104] WEBDOM-357: Integrate custom tokens in the core bundle, so we don't have to support them in the separate bundles. --- Service/TemplateService.php | 141 +++++++++++------- Service/TokenService.php | 7 +- Tests/Entity/TokenTest.php | 23 +++ Tests/Form/Type/TokenFormType.php | 52 +++++++ ...erviceTest.php => TemplateServiceTest.php} | 0 Tests/Service/TokenServiceTest.php | 61 ++++++++ 6 files changed, 229 insertions(+), 55 deletions(-) create mode 100644 Tests/Entity/TokenTest.php create mode 100644 Tests/Form/Type/TokenFormType.php rename Tests/Service/{TestTemplateServiceTest.php => TemplateServiceTest.php} (100%) create mode 100644 Tests/Service/TokenServiceTest.php diff --git a/Service/TemplateService.php b/Service/TemplateService.php index 55840c9..dd3750a 100644 --- a/Service/TemplateService.php +++ b/Service/TemplateService.php @@ -13,6 +13,13 @@ class TemplateService { + protected $tokenService; + + public function __construct(TokenService $tokenService) + { + $this->tokenService = $tokenService; + } + /** * @param string $text * @param array $entities @@ -22,79 +29,111 @@ public function replaceKeys($text, array $entities = array()): string { $hasMatches = false; + // Loop over user created tokens. + foreach ($this->tokenService->getTemplateReplacements() as $templateReplacementKey => $templateReplacementValueCallback) { + $text = $this->doReplacement( + $text, + [ + 'prefix' => 'token', + 'entity' => $this->tokenService, + 'key' => $templateReplacementKey, + 'callback' => $templateReplacementValueCallback, + ], + $hasMatches + ); + } // Loop over all entities foreach ($entities as $entityPrefix => $entity) { if (!$entity instanceof TemplateInterface) { throw new TemplateException('This object doesn\'t implement the TemplateInterface'); } - foreach ($entity::getTemplateReplacements() as $templateReplacementKey => $templateReplacementValue) { - - // Define the replacement arguments - $replacementArguments = []; + foreach ($entity::getTemplateReplacements() as $templateReplacementKey => $templateReplacementValueCallback) { + $text = $this->doReplacement( + $text, + [ + 'prefix' => $entityPrefix, + 'entity' => $entity, + 'key' => $templateReplacementKey, + 'callback' => $templateReplacementValueCallback, + ], + $hasMatches + ); + } + } - preg_match('#\((.*?)\)#', $templateReplacementKey, $match); - if (isset($match[1]) && $match[1] != '') { - $replacementArguments = explode(',', $match[1]); - } + // Recursivly go trough this function until no matches are found + if ($hasMatches) { + $text = $this->replaceKeys($text, $entities); + } - // Complete the pattern and escape all existing special characters - $pattern = '[[ ' . $entityPrefix . ':' . $templateReplacementKey . ' ]]'; - $pattern = str_replace(['(', ')', '[', ']'], ['\(', '\)', '\[', '\]'], $pattern); - $replacePattern = $pattern; + return $text; + } - // Get all the arguments out of the pattern so we can match them with the real arguments - foreach ($replacementArguments as $replacementArgument) { - $pattern = str_replace($replacementArgument, '([^)]*)', $pattern); - } + protected function doReplacement(string $text, array $data, &$hasMatches) + { + $entityPrefix = $data['prefix']; + $entity = $data['entity']; + $templateReplacementKey = $data['key']; + $templateReplacementValueCallback = $data['callback']; + // Define the replacement arguments + $replacementArguments = []; + $match = []; + preg_match('#\((.*?)\)#', $templateReplacementKey, $match); + if (isset($match[1]) && $match[1] != '') { + $replacementArguments = explode(',', $match[1]); + } - // Check if the pattern exists in our text - $hasMatch = preg_match('/' . $pattern . '/', $text, $matches); + // Complete the pattern and escape all existing special characters + $pattern = '[[ ' . $entityPrefix . ':' . $templateReplacementKey . ' ]]'; + $pattern = str_replace(['(', ')', '[', ']'], ['\(', '\)', '\[', '\]'], $pattern); + $replacePattern = $pattern; - // If we have a match for the pattern we substitute it - if ($hasMatch) { - $hasMatches = true; + // Get all the arguments out of the pattern so we can match them with the real arguments + foreach ($replacementArguments as $replacementArgument) { + $pattern = str_replace($replacementArgument, '([^)]*)', $pattern); + } - // The value can be called recursive - $passingValue = $entity; + // Check if the pattern exists in our text + $matches = []; + $hasMatch = preg_match('/' . $pattern . '/', $text, $matches); - // Get a key value pair of all arguments - foreach ($replacementArguments as $key => $value) { - $replacementArguments[$value] = $matches[$key + 1]; - $replacePattern = str_replace($replacementArguments[$key], $matches[$key + 1 ], $replacePattern); - } + // If we have a match for the pattern we substitute it + if (!$hasMatch) { + return; + } + $hasMatches = true; - // Get all functions that should be executed - $functions = explode('.', $templateReplacementValue); + // The value can be called recursive + $passingValue = $entity; - // Execute these functions on the defined entity with the discovered arguments - foreach ($functions as $function) { - preg_match('/^([a-zA-Z]*)(\((.*)\))?/', $function, $result); + // Get a key value pair of all arguments + foreach ($replacementArguments as $key => $value) { + $replacementArguments[$value] = $matches[$key + 1]; + $replacePattern = str_replace($replacementArguments[$key], $matches[$key + 1 ], $replacePattern); + } - $functionArguments = []; - $methodName = $result[1]; - // Get the arguments and replace them by the real values if they are present - if (isset($result[3]) && $result[3] != '') { - $functionArguments = explode(',', $result[3]); - foreach ($functionArguments as $key => $value) { - $functionArguments[$key] = $replacementArguments[$value]; - } - } + // Get all functions that should be executed + $functions = explode('.', $templateReplacementValueCallback); - $passingValue = call_user_func_array(array($passingValue, $methodName), $functionArguments); - } + // Execute these functions on the defined entity with the discovered arguments + foreach ($functions as $function) { + preg_match('/^([a-zA-Z]*)(\((.*)\))?/', $function, $result); - // Replace the pattern with the found value - $text = preg_replace('/' . $replacePattern . '/', $passingValue, $text); + $functionArguments = []; + $methodName = $result[1]; + // Get the arguments and replace them by the real values if they are present + if (isset($result[3]) && $result[3] != '') { + $functionArguments = explode(',', $result[3]); + foreach ($functionArguments as $key => $value) { + $functionArguments[$key] = $replacementArguments[$value]; } } - } - // Recursivly go trough this function until no matches are found - if ($hasMatches) { - $text = $this->replaceKeys($text, $entities); + $passingValue = call_user_func_array(array($passingValue, $methodName), $functionArguments); } - return $text; + // Replace the pattern with the found value + return preg_replace('/' . $replacePattern . '/', $passingValue, $text); } } diff --git a/Service/TokenService.php b/Service/TokenService.php index 629f8d9..c0c51aa 100644 --- a/Service/TokenService.php +++ b/Service/TokenService.php @@ -6,7 +6,6 @@ use Camel\CaseTransformer; use Camel\Format\SnakeCase; use Camel\Format\StudlyCaps; -use DigipolisGent\Domainator9k\CoreBundle\Entity\TemplateInterface; use DigipolisGent\Domainator9k\CoreBundle\Entity\Token; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; @@ -15,7 +14,7 @@ * Class TokenService * @package DigipolisGent\Domainator9k\CoreBundle\Service */ -class TokenService implements TemplateInterface +class TokenService { /** @@ -34,7 +33,7 @@ public function __construct(EntityManager $entityManager) $this->caseTransformer = new CaseTransformer(new SnakeCase(), new StudlyCaps()); } - public static function getTemplateReplacements(): array + public function getTemplateReplacements(): array { $tokens = $this->repository->findAll(); $replacements = []; @@ -51,7 +50,7 @@ public function __call(string $name, array $arguments) throw new \BadMethodCallException('Call to undefined method ' . static::class . '::' . $name); } - $replacements = static::getTemplateReplacements(); + $replacements = $this->getTemplateReplacements(); $tokenName = array_search($name . '()', $replacements); if ($tokenName === false) { throw new \BadMethodCallException('Call to undefined method ' . static::class . '::' . $name); diff --git a/Tests/Entity/TokenTest.php b/Tests/Entity/TokenTest.php new file mode 100644 index 0000000..b55a06b --- /dev/null +++ b/Tests/Entity/TokenTest.php @@ -0,0 +1,23 @@ +assertSame($token, $token->setName($name)); + $this->assertSame($token, $token->setValue($value)); + $this->assertEquals($token->getName(), $name); + $this->assertEquals($token->getValue(), $value); + } + +} diff --git a/Tests/Form/Type/TokenFormType.php b/Tests/Form/Type/TokenFormType.php new file mode 100644 index 0000000..78abec7 --- /dev/null +++ b/Tests/Form/Type/TokenFormType.php @@ -0,0 +1,52 @@ +getOptionsResolverMock(); + + $optionsResolver + ->expects($this->at(0)) + ->method('setDefault') + ->with('data_class', Token::class); + + $formType = new TokenFormType($this->getFormServiceMock()); + $formType->configureOptions($optionsResolver); + } + + public function testBuildForm() + { + $formBuilder = $this->getFormBuilderMock(); + + $arguments = [ + 'name', + 'value', + ]; + + $index = 0; + + foreach ($arguments as $argument) { + $formBuilder + ->expects($this->at($index)) + ->method('add') + ->with($argument); + + $index++; + } + + $formBuilder + ->expects($this->at($index)) + ->method('addEventSubscriber'); + + $formType = new TokenFormType($this->getFormServiceMock()); + $formType->buildForm($formBuilder, []); + } +} diff --git a/Tests/Service/TestTemplateServiceTest.php b/Tests/Service/TemplateServiceTest.php similarity index 100% rename from Tests/Service/TestTemplateServiceTest.php rename to Tests/Service/TemplateServiceTest.php diff --git a/Tests/Service/TokenServiceTest.php b/Tests/Service/TokenServiceTest.php new file mode 100644 index 0000000..99ad7ae --- /dev/null +++ b/Tests/Service/TokenServiceTest.php @@ -0,0 +1,61 @@ +repository = $this + ->getMockBuilder(EntityRepository::class) + ->disableOriginalConstructor() + ->getMock(); + $this->entityManager = $this + ->getMockBuilder(EntityManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->entityManager + ->expects($this->once()) + ->method('getRepository') + ->with(Token::class) + ->willReturn($this->repository); + $this->tokenService = new TokenService($this->entityManager); + } + + protected function testGetTemplateReplacements() + { + $name = uniqid(); + $value = uniqid(); + $token = new Token(); + $token->setName($name); + $token->setValue($value); + $this->repository->expects($this->once())->method('findAll')->willReturn([$token]); + $this->assertEquals([$name . '()' => 'get' . ucfirst($name) . '()'], $this->tokenService->getTemplateReplacements()); + } + + protected function testMagicCallMethod() + { + $name = uniqid(); + $value = uniqid(); + $token = new Token(); + $token->setName($name); + $token->setValue($value); + $this->repository->expects($this->once())->method('findAll')->willReturn([$token]); + $this->repository->expects($this->once())->method('findOneBy')->with(['name' => $name])->willReturn($token); + $method = 'get' . ucfirst($name); + $this->assertEquals($value, $this->tokenService->{$method}()); + } +} From 54b19835a16acdaf925f42a083a542322d67330f Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 15:05:14 +0200 Subject: [PATCH 029/104] WEBDOM-357: Code style fixes. --- Service/TemplateService.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Service/TemplateService.php b/Service/TemplateService.php index dd3750a..79d0d3b 100644 --- a/Service/TemplateService.php +++ b/Service/TemplateService.php @@ -34,15 +34,16 @@ public function replaceKeys($text, array $entities = array()): string $text = $this->doReplacement( $text, [ - 'prefix' => 'token', - 'entity' => $this->tokenService, - 'key' => $templateReplacementKey, - 'callback' => $templateReplacementValueCallback, + 'prefix' => 'token', + 'entity' => $this->tokenService, + 'key' => $templateReplacementKey, + 'callback' => $templateReplacementValueCallback, ], $hasMatches ); } - // Loop over all entities + + // Loop over all entities. foreach ($entities as $entityPrefix => $entity) { if (!$entity instanceof TemplateInterface) { throw new TemplateException('This object doesn\'t implement the TemplateInterface'); @@ -52,13 +53,13 @@ public function replaceKeys($text, array $entities = array()): string $text = $this->doReplacement( $text, [ - 'prefix' => $entityPrefix, - 'entity' => $entity, - 'key' => $templateReplacementKey, - 'callback' => $templateReplacementValueCallback, + 'prefix' => $entityPrefix, + 'entity' => $entity, + 'key' => $templateReplacementKey, + 'callback' => $templateReplacementValueCallback, ], $hasMatches - ); + ); } } From c0e63abd09ed8a51817f242f504bf0e2d730ef65 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 15:37:59 +0200 Subject: [PATCH 030/104] WEBDOM-357: Test fixes. --- Tests/Service/TemplateServiceTest.php | 38 ++++++++++++++++++++++----- Tests/Service/TokenServiceTest.php | 4 +-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Tests/Service/TemplateServiceTest.php b/Tests/Service/TemplateServiceTest.php index 3130563..1941b5d 100644 --- a/Tests/Service/TemplateServiceTest.php +++ b/Tests/Service/TemplateServiceTest.php @@ -13,12 +13,27 @@ class TemplateServiceTest extends TestCase { + protected $token; + protected $tokenService; + + protected function setUp() + { + parent::setUp(); + $name = uniqid(); + $value = uniqid(); + $token = new Token(); + $token->setName($name); + $token->setValue($value); + $this->token = $token; + $this->tokenService = $this->getTokenServiceMock(); + } + /** * @expectedException \DigipolisGent\Domainator9k\CoreBundle\Exception\TemplateException */ public function testReplaceKeysWithInvalidEntity() { - $templateService = new TemplateService(); + $templateService = new TemplateService($this->tokenService); $text = <<tokenService); + $name = ucfirst($this->token->getName()); + $value = $this->token->getValue(); $text = <<assertEquals($expected, $actual); @@ -62,7 +80,9 @@ public function testReplaceKeysWithValidEntity() public function testReplaceKeysRecursively() { - $templateService = new TemplateService(); + $templateService = new TemplateService($this->tokenService); + $name = ucfirst($this->token->getName()); + $value = $this->token->getValue(); $text = <<setTitle('Qux title example'); + $qux->setTitle("[[ token:get{$name}]]"); $qux->setSubTitle('[[ foo:primary() ]]'); $foo = new Foo(); @@ -85,10 +105,16 @@ public function testReplaceKeysRecursively() $actual = $templateService->replaceKeys($text, $entities); $expected = <<assertEquals($expected, $actual); } + + protected function getTokenServiceMock() + { + $this->repository->expects($this->any())->method('findAll')->willReturn([$this->token]); + $this->repository->expects($this->any())->method('findOneBy')->with(['name' => $this->token->getName()])->willReturn($this->token); + } } diff --git a/Tests/Service/TokenServiceTest.php b/Tests/Service/TokenServiceTest.php index 99ad7ae..8eea0cf 100644 --- a/Tests/Service/TokenServiceTest.php +++ b/Tests/Service/TokenServiceTest.php @@ -35,7 +35,7 @@ protected function setUp() $this->tokenService = new TokenService($this->entityManager); } - protected function testGetTemplateReplacements() + public function testGetTemplateReplacements() { $name = uniqid(); $value = uniqid(); @@ -46,7 +46,7 @@ protected function testGetTemplateReplacements() $this->assertEquals([$name . '()' => 'get' . ucfirst($name) . '()'], $this->tokenService->getTemplateReplacements()); } - protected function testMagicCallMethod() + public function testMagicCallMethod() { $name = uniqid(); $value = uniqid(); From b5d92ba9f22b8f83963f3cf71ef2b64b509991be Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 15:41:51 +0200 Subject: [PATCH 031/104] WEBDOM-357: Fix repacement keys should look like function calls. --- Service/TokenService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Service/TokenService.php b/Service/TokenService.php index c0c51aa..33c06a1 100644 --- a/Service/TokenService.php +++ b/Service/TokenService.php @@ -38,7 +38,7 @@ public function getTemplateReplacements(): array $tokens = $this->repository->findAll(); $replacements = []; foreach ($tokens as $token) { - $replacements[$token->getName()] = 'get' . $this->caseTransformer->transform($token->getName()) . '()'; + $replacements[$token->getName() . '()'] = 'get' . $this->caseTransformer->transform($token->getName()) . '()'; } return $replacements; From 93c9e240d05fe4e98929a4c9aab6c750256b74cd Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 16:29:14 +0200 Subject: [PATCH 032/104] WEBDOM-357: Fix tests. --- Service/TemplateService.php | 2 +- Service/TokenService.php | 1 + Tests/Entity/TokenTest.php | 4 +-- Tests/Service/TemplateServiceTest.php | 43 +++++++++++++++++---------- Tests/Service/TokenServiceTest.php | 12 ++++---- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/Service/TemplateService.php b/Service/TemplateService.php index 79d0d3b..4be7346 100644 --- a/Service/TemplateService.php +++ b/Service/TemplateService.php @@ -101,7 +101,7 @@ protected function doReplacement(string $text, array $data, &$hasMatches) // If we have a match for the pattern we substitute it if (!$hasMatch) { - return; + return $text; } $hasMatches = true; diff --git a/Service/TokenService.php b/Service/TokenService.php index 33c06a1..66e82e2 100644 --- a/Service/TokenService.php +++ b/Service/TokenService.php @@ -55,6 +55,7 @@ public function __call(string $name, array $arguments) if ($tokenName === false) { throw new \BadMethodCallException('Call to undefined method ' . static::class . '::' . $name); } + $tokenName = substr($tokenName, 0, -2); $token = $this->repository->findOneBy(['name' => $tokenName]); if (!$token) { diff --git a/Tests/Entity/TokenTest.php b/Tests/Entity/TokenTest.php index b55a06b..40a1837 100644 --- a/Tests/Entity/TokenTest.php +++ b/Tests/Entity/TokenTest.php @@ -11,8 +11,8 @@ class TokenTest extends TestCase public function testGettersAndSetters() { - $name = uniqid(); - $value= uniqid(); + $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; + $value= substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; $token = new Token(); $this->assertSame($token, $token->setName($name)); $this->assertSame($token, $token->setValue($value)); diff --git a/Tests/Service/TemplateServiceTest.php b/Tests/Service/TemplateServiceTest.php index 1941b5d..2550b81 100644 --- a/Tests/Service/TemplateServiceTest.php +++ b/Tests/Service/TemplateServiceTest.php @@ -3,11 +3,14 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Tests\Service; - +use DigipolisGent\Domainator9k\CoreBundle\Entity\Token; use DigipolisGent\Domainator9k\CoreBundle\Service\TemplateService; +use DigipolisGent\Domainator9k\CoreBundle\Service\TokenService; use DigipolisGent\Domainator9k\CoreBundle\Tests\Fixtures\Entity\Bar; use DigipolisGent\Domainator9k\CoreBundle\Tests\Fixtures\Entity\Foo; use DigipolisGent\Domainator9k\CoreBundle\Tests\Fixtures\Entity\Qux; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityRepository; use PHPUnit\Framework\TestCase; class TemplateServiceTest extends TestCase @@ -15,17 +18,31 @@ class TemplateServiceTest extends TestCase protected $token; protected $tokenService; + protected $repository; protected function setUp() { parent::setUp(); - $name = uniqid(); - $value = uniqid(); $token = new Token(); - $token->setName($name); - $token->setValue($value); + $token->setName(substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10)); + $token->setValue(substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10)); $this->token = $token; - $this->tokenService = $this->getTokenServiceMock(); + $this->repository = $this + ->getMockBuilder(EntityRepository::class) + ->disableOriginalConstructor() + ->getMock(); + $this->repository->expects($this->any())->method('findAll')->willReturn([$token]); + $this->repository->expects($this->any())->method('findOneBy')->with(['name' => $token->getName()])->willReturn($token); + $this->entityManager = $this + ->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->entityManager + ->expects($this->once()) + ->method('getRepository') + ->with(Token::class) + ->willReturn($this->repository); + $this->tokenService = new TokenService($this->entityManager); } /** @@ -49,13 +66,13 @@ public function testReplaceKeysWithInvalidEntity() public function testReplaceKeysWithValidEntity() { $templateService = new TemplateService($this->tokenService); - $name = ucfirst($this->token->getName()); + $name = $this->token->getName(); $value = $this->token->getValue(); $text = <<tokenService); - $name = ucfirst($this->token->getName()); + $name = $this->token->getName(); $value = $this->token->getValue(); $text = <<setTitle("[[ token:get{$name}]]"); + $qux->setTitle("[[ token:{$name}() ]]"); $qux->setSubTitle('[[ foo:primary() ]]'); $foo = new Foo(); @@ -111,10 +128,4 @@ public function testReplaceKeysRecursively() $this->assertEquals($expected, $actual); } - - protected function getTokenServiceMock() - { - $this->repository->expects($this->any())->method('findAll')->willReturn([$this->token]); - $this->repository->expects($this->any())->method('findOneBy')->with(['name' => $this->token->getName()])->willReturn($this->token); - } } diff --git a/Tests/Service/TokenServiceTest.php b/Tests/Service/TokenServiceTest.php index 8eea0cf..660d31f 100644 --- a/Tests/Service/TokenServiceTest.php +++ b/Tests/Service/TokenServiceTest.php @@ -5,7 +5,7 @@ use DigipolisGent\Domainator9k\CoreBundle\Entity\Token; use DigipolisGent\Domainator9k\CoreBundle\Service\TokenService; -use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use PHPUnit\Framework\TestCase; @@ -24,7 +24,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->entityManager = $this - ->getMockBuilder(EntityManagerInterface::class) + ->getMockBuilder(EntityManager::class) ->disableOriginalConstructor() ->getMock(); $this->entityManager @@ -37,8 +37,8 @@ protected function setUp() public function testGetTemplateReplacements() { - $name = uniqid(); - $value = uniqid(); + $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10); + $value = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10); $token = new Token(); $token->setName($name); $token->setValue($value); @@ -48,8 +48,8 @@ public function testGetTemplateReplacements() public function testMagicCallMethod() { - $name = uniqid(); - $value = uniqid(); + $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; + $value = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; $token = new Token(); $token->setName($name); $token->setValue($value); From 46980111dc97499ce258812830a42dff6bc2b92f Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 16:45:57 +0200 Subject: [PATCH 033/104] WEBDOM-357: Rename TokenFormType.php to TokenFormTypeTest.php --- Tests/Form/Type/{TokenFormType.php => TokenFormTypeTest.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/Form/Type/{TokenFormType.php => TokenFormTypeTest.php} (100%) diff --git a/Tests/Form/Type/TokenFormType.php b/Tests/Form/Type/TokenFormTypeTest.php similarity index 100% rename from Tests/Form/Type/TokenFormType.php rename to Tests/Form/Type/TokenFormTypeTest.php From b11396b4c41848f2cfd90f67f2144707fc4c6a90 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 17 Apr 2018 17:18:01 +0200 Subject: [PATCH 034/104] WEBDOM-357: Code style fixes. --- Tests/Entity/TokenTest.php | 4 ++-- Tests/Service/TokenServiceTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Entity/TokenTest.php b/Tests/Entity/TokenTest.php index 40a1837..a83d8b0 100644 --- a/Tests/Entity/TokenTest.php +++ b/Tests/Entity/TokenTest.php @@ -11,8 +11,8 @@ class TokenTest extends TestCase public function testGettersAndSetters() { - $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; - $value= substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; + $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10); + $value= substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10); $token = new Token(); $this->assertSame($token, $token->setName($name)); $this->assertSame($token, $token->setValue($value)); diff --git a/Tests/Service/TokenServiceTest.php b/Tests/Service/TokenServiceTest.php index 660d31f..73ea705 100644 --- a/Tests/Service/TokenServiceTest.php +++ b/Tests/Service/TokenServiceTest.php @@ -48,8 +48,8 @@ public function testGetTemplateReplacements() public function testMagicCallMethod() { - $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; - $value = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);; + $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10); + $value = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10); $token = new Token(); $token->setName($name); $token->setValue($value); From e4cd0dc22c214043847dc00ab02014721f04977c Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Wed, 16 May 2018 11:25:38 +0200 Subject: [PATCH 035/104] WEBDOM-357: Updated template service to some more human readable code. --- Service/TemplateService.php | 258 ++++++++++++++++++++++++------------ 1 file changed, 175 insertions(+), 83 deletions(-) diff --git a/Service/TemplateService.php b/Service/TemplateService.php index 4be7346..9e55f65 100644 --- a/Service/TemplateService.php +++ b/Service/TemplateService.php @@ -8,133 +8,225 @@ /** * Class TemplateService + * * @package DigipolisGent\Domainator9k\CoreBundle\Service */ class TemplateService { + /** + * The service for custom tokens. + * + * @var TokenService + */ protected $tokenService; + /** + * The replacements. + * + * @var array + */ + protected $replacements; + + /** + * Class constructor. + * + * @param TokenService $tokenService + * The token service. + */ public function __construct(TokenService $tokenService) { $this->tokenService = $tokenService; } /** + * Replace all keys. + * * @param string $text + * The text to replace in. * @param array $entities + * The template entities keyed by prefix. + * * @return string + * The processed text. */ public function replaceKeys($text, array $entities = array()): string { - $hasMatches = false; - - // Loop over user created tokens. - foreach ($this->tokenService->getTemplateReplacements() as $templateReplacementKey => $templateReplacementValueCallback) { - $text = $this->doReplacement( - $text, - [ - 'prefix' => 'token', - 'entity' => $this->tokenService, - 'key' => $templateReplacementKey, - 'callback' => $templateReplacementValueCallback, - ], - $hasMatches - ); + // Register the replacements. + $this->resetReplacements(); + foreach ($entities as $type => $entity) { + $this->registerReplacements($type, $entity); } - // Loop over all entities. - foreach ($entities as $entityPrefix => $entity) { - if (!$entity instanceof TemplateInterface) { - throw new TemplateException('This object doesn\'t implement the TemplateInterface'); - } + // Replace the tokens. + do { + $result = preg_replace_callback('#\[\[[ ]*([a-zA-Z][a-zA-Z0-9_]*):([a-zA-Z][a-zA-Z0-9_]*)\([ ]*([a-zA-Z][a-zA-Z0-9_]*(?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)*)?[ ]*\)[ ]*\]\]#', [$this, 'doReplace'], $text); - foreach ($entity::getTemplateReplacements() as $templateReplacementKey => $templateReplacementValueCallback) { - $text = $this->doReplacement( - $text, - [ - 'prefix' => $entityPrefix, - 'entity' => $entity, - 'key' => $templateReplacementKey, - 'callback' => $templateReplacementValueCallback, - ], - $hasMatches - ); + if ($result === $text) { + break; } - } - // Recursivly go trough this function until no matches are found - if ($hasMatches) { - $text = $this->replaceKeys($text, $entities); - } + $text = $result; + } while (TRUE); return $text; } - protected function doReplacement(string $text, array $data, &$hasMatches) + /** + * Replace a key match. + * + * @param array $matches + * The replacement matches. + * + * @return string + * The replacement text. + */ + protected function doReplace(array $matches): string { - $entityPrefix = $data['prefix']; - $entity = $data['entity']; - $templateReplacementKey = $data['key']; - $templateReplacementValueCallback = $data['callback']; - // Define the replacement arguments - $replacementArguments = []; - $match = []; - preg_match('#\((.*?)\)#', $templateReplacementKey, $match); - if (isset($match[1]) && $match[1] != '') { - $replacementArguments = explode(',', $match[1]); - } + // Use readable variables names. + $matches[] = null; + list ($original, $type, $key, $params) = $matches; + + if (isset($this->replacements[$type][$key])) { + // Get the replacement. + $replacement = $this->replacements[$type][$key]; + + // Prepare the parameters. + if (!$replacement['params'] || $params === '') { + $params = []; + } + else { + $params = explode(',', str_replace(' ', '', $params)); + $count1 = count($params); + $count2 = count($replacement['params']); + + // Ensure both arrays have the same number of parameters. + if ($count1 > $count2) { + $params = array_slice($params, 0, $count2); + } + elseif ($count2 > $count1) { + $params = array_merge($params, array_fill(0, ($count2 - $count1), null)); + } - // Complete the pattern and escape all existing special characters - $pattern = '[[ ' . $entityPrefix . ':' . $templateReplacementKey . ' ]]'; - $pattern = str_replace(['(', ')', '[', ']'], ['\(', '\)', '\[', '\]'], $pattern); - $replacePattern = $pattern; + // Create an associative array. + $params = array_combine($replacement['params'], $params); + } + + $result = $replacement['object']; + + foreach ($replacement['callbacks'] as $callback => $callbackParams) { + // Get the parameters. + foreach ($callbackParams as $name => &$value) { + if (array_key_exists($name, $params)) { + $value = $params[$name]; + } + } + + // Execute the callback. + $result = call_user_func_array([$result, $callback], $callbackParams); + } - // Get all the arguments out of the pattern so we can match them with the real arguments - foreach ($replacementArguments as $replacementArgument) { - $pattern = str_replace($replacementArgument, '([^)]*)', $pattern); + return $result; } - // Check if the pattern exists in our text - $matches = []; - $hasMatch = preg_match('/' . $pattern . '/', $text, $matches); + return $original; + } - // If we have a match for the pattern we substitute it - if (!$hasMatch) { - return $text; + /** + * Reset the replacements. + */ + protected function resetReplacements() + { + if ($this->replacements === null) { + $this->replacements = []; + $this->registerReplacements('token', $this->tokenService); } - $hasMatches = true; + else { + $this->replacements = [ + 'token' => $this->replacements['token'], + ]; + } + } - // The value can be called recursive - $passingValue = $entity; + /** + * Register new replacements. + * + * @param string $type + * The replacement type. + * @param TemplateInterface|TokenService $object + * The object to use. + * @param array $replacements + * Array of replacements, leave null to get them from the object. + */ + protected function registerReplacements(string $type, $object, array $replacements = null) + { + // Initialize the replacements. + if ($this->replacements === null) { + $this->resetReplacements(); + } - // Get a key value pair of all arguments - foreach ($replacementArguments as $key => $value) { - $replacementArguments[$value] = $matches[$key + 1]; - $replacePattern = str_replace($replacementArguments[$key], $matches[$key + 1 ], $replacePattern); + // Get the default replacements. + if ($replacements === null) { + if ($object instanceof TemplateInterface) { + $replacements = $object::getTemplateReplacements(); + } + elseif ($object instanceof TokenService) { + $replacements = $object->getTemplateReplacements(); + } + else { + throw new \InvalidArgumentException("The object doesn't specify default replacements."); + } } - // Get all functions that should be executed - $functions = explode('.', $templateReplacementValueCallback); + foreach ($replacements as $replacementKey => $replacementValueCallback) { + // Extract the key and parameters. + if (!preg_match('#^([a-zA-Z][a-zA-Z0-9_]*)\([ ]*([a-zA-Z][a-zA-Z0-9_]*(?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)*)?[ ]*\)$#', $replacementKey, $matches)) { + continue; + } + + $key = $matches[1]; + + // Prepare the parameters. + if (isset($matches[2])) { + $keyParams = explode(',', str_replace(' ', '', $matches[2])); + } + else { + $keyParams = []; + } + + // Extract the callbacks. + $callbacks = []; + $replacementValueCallback = explode('.', $replacementValueCallback); + foreach ($replacementValueCallback as $callback) { + // Extract the method and parameters. + if (!preg_match('#^([a-zA-Z][a-zA-Z0-9_]*)\([ ]*([a-zA-Z][a-zA-Z0-9_]*(?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)*)?[ ]*\)$#', $callback, $matches)) { + continue 2; + } + + // Prepare the parameters. + if (isset($matches[2])) { + $params = explode(',', str_replace(' ', '', $matches[2])); - // Execute these functions on the defined entity with the discovered arguments - foreach ($functions as $function) { - preg_match('/^([a-zA-Z]*)(\((.*)\))?/', $function, $result); + if (array_diff($params, $keyParams)) { + throw new TemplateException('The replacement value callback uses unknown parameters.'); + } - $functionArguments = []; - $methodName = $result[1]; - // Get the arguments and replace them by the real values if they are present - if (isset($result[3]) && $result[3] != '') { - $functionArguments = explode(',', $result[3]); - foreach ($functionArguments as $key => $value) { - $functionArguments[$key] = $replacementArguments[$value]; + $params = array_fill_keys($params, null); + } + else { + $params = []; } + + $callbacks[$matches[1]] = $params; } - $passingValue = call_user_func_array(array($passingValue, $methodName), $functionArguments); + // Add the replacement. + $this->replacements[$type][$key] = [ + 'params' => $keyParams, + 'callbacks' => $callbacks, + 'object' => $object, + ]; } - - // Replace the pattern with the found value - return preg_replace('/' . $replacePattern . '/', $passingValue, $text); } + } From 95ef685c0bf315b59b491d515c92c458a92ad4eb Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Wed, 16 May 2018 12:51:55 +0200 Subject: [PATCH 036/104] WEBDOM-357: Fixed failing tests. --- Service/TemplateService.php | 44 +++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/Service/TemplateService.php b/Service/TemplateService.php index 9e55f65..5d0be6e 100644 --- a/Service/TemplateService.php +++ b/Service/TemplateService.php @@ -60,7 +60,23 @@ public function replaceKeys($text, array $entities = array()): string // Replace the tokens. do { - $result = preg_replace_callback('#\[\[[ ]*([a-zA-Z][a-zA-Z0-9_]*):([a-zA-Z][a-zA-Z0-9_]*)\([ ]*([a-zA-Z][a-zA-Z0-9_]*(?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)*)?[ ]*\)[ ]*\]\]#', [$this, 'doReplace'], $text); + $result = preg_replace_callback('# + \[\[ + [ ]* + ([a-zA-Z][a-zA-Z0-9_]*) + : + ([a-zA-Z][a-zA-Z0-9_]*) + \( + [ ]* + ( + [^,\s]+ + (?:[ ]*,[ ]*[^,\s]+)* + )? + [ ]* + \) + [ ]* + \]\] + #x', [$this, 'doReplace'], $text); if ($result === $text) { break; @@ -174,13 +190,23 @@ protected function registerReplacements(string $type, $object, array $replacemen $replacements = $object->getTemplateReplacements(); } else { - throw new \InvalidArgumentException("The object doesn't specify default replacements."); + throw new TemplateException("The object doesn't specify default replacements."); } } foreach ($replacements as $replacementKey => $replacementValueCallback) { // Extract the key and parameters. - if (!preg_match('#^([a-zA-Z][a-zA-Z0-9_]*)\([ ]*([a-zA-Z][a-zA-Z0-9_]*(?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)*)?[ ]*\)$#', $replacementKey, $matches)) { + if (!preg_match('#^ + ([a-zA-Z][a-zA-Z0-9_]*) + \( + [ ]* + ( + [a-zA-Z][a-zA-Z0-9_]* + (?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)* + )? + [ ]* + \) + $#x', $replacementKey, $matches)) { continue; } @@ -199,7 +225,17 @@ protected function registerReplacements(string $type, $object, array $replacemen $replacementValueCallback = explode('.', $replacementValueCallback); foreach ($replacementValueCallback as $callback) { // Extract the method and parameters. - if (!preg_match('#^([a-zA-Z][a-zA-Z0-9_]*)\([ ]*([a-zA-Z][a-zA-Z0-9_]*(?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)*)?[ ]*\)$#', $callback, $matches)) { + if (!preg_match('#^ + ([a-zA-Z][a-zA-Z0-9_]*) + \( + [ ]* + ( + [a-zA-Z][a-zA-Z0-9_]* + (?:[ ]*,[ ]*[a-zA-Z][a-zA-Z0-9_]*)* + )? + [ ]* + \) + $#x', $callback, $matches)) { continue 2; } From 7433c9b399c5ff57dee0ab1c29f377ae4a3cbc7d Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Wed, 16 May 2018 12:55:31 +0200 Subject: [PATCH 037/104] WEBDOM-357: Fixed code formatting. --- Service/TemplateService.php | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Service/TemplateService.php b/Service/TemplateService.php index 5d0be6e..7454f63 100644 --- a/Service/TemplateService.php +++ b/Service/TemplateService.php @@ -83,7 +83,7 @@ public function replaceKeys($text, array $entities = array()): string } $text = $result; - } while (TRUE); + } while (true); return $text; } @@ -110,8 +110,7 @@ protected function doReplace(array $matches): string // Prepare the parameters. if (!$replacement['params'] || $params === '') { $params = []; - } - else { + } else { $params = explode(',', str_replace(' ', '', $params)); $count1 = count($params); $count2 = count($replacement['params']); @@ -119,8 +118,7 @@ protected function doReplace(array $matches): string // Ensure both arrays have the same number of parameters. if ($count1 > $count2) { $params = array_slice($params, 0, $count2); - } - elseif ($count2 > $count1) { + } elseif ($count2 > $count1) { $params = array_merge($params, array_fill(0, ($count2 - $count1), null)); } @@ -156,8 +154,7 @@ protected function resetReplacements() if ($this->replacements === null) { $this->replacements = []; $this->registerReplacements('token', $this->tokenService); - } - else { + } else { $this->replacements = [ 'token' => $this->replacements['token'], ]; @@ -185,11 +182,9 @@ protected function registerReplacements(string $type, $object, array $replacemen if ($replacements === null) { if ($object instanceof TemplateInterface) { $replacements = $object::getTemplateReplacements(); - } - elseif ($object instanceof TokenService) { + } elseif ($object instanceof TokenService) { $replacements = $object->getTemplateReplacements(); - } - else { + } else { throw new TemplateException("The object doesn't specify default replacements."); } } @@ -215,8 +210,7 @@ protected function registerReplacements(string $type, $object, array $replacemen // Prepare the parameters. if (isset($matches[2])) { $keyParams = explode(',', str_replace(' ', '', $matches[2])); - } - else { + } else { $keyParams = []; } @@ -248,8 +242,7 @@ protected function registerReplacements(string $type, $object, array $replacemen } $params = array_fill_keys($params, null); - } - else { + } else { $params = []; } From da063de8331ca54ecf3f5262405cf841690073b4 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 10:47:44 +0200 Subject: [PATCH 038/104] WEBDOM-380: Updated commands, services and eventlisteners. --- Command/AbstractCommand.php | 23 ++ Command/BuildCommand.php | 25 +- Command/DestroyCommand.php | 25 +- Entity/Task.php | 92 +++---- Event/AbstractEvent.php | 21 +- EventListener/BuildEventListener.php | 56 ---- EventListener/DestroyEventListener.php | 58 ---- Resources/config/services.yml | 10 +- Service/TaskLoggerService.php | 55 ---- Service/TaskService.php | 353 +++++++++++++++++++++++++ 10 files changed, 462 insertions(+), 256 deletions(-) create mode 100644 Command/AbstractCommand.php delete mode 100644 EventListener/BuildEventListener.php delete mode 100644 EventListener/DestroyEventListener.php delete mode 100644 Service/TaskLoggerService.php create mode 100644 Service/TaskService.php diff --git a/Command/AbstractCommand.php b/Command/AbstractCommand.php new file mode 100644 index 0000000..317a748 --- /dev/null +++ b/Command/AbstractCommand.php @@ -0,0 +1,23 @@ +getContainer() + ->get(TaskService::class) + ->runNext($type); + } +} diff --git a/Command/BuildCommand.php b/Command/BuildCommand.php index 25e5b4b..22daa4f 100644 --- a/Command/BuildCommand.php +++ b/Command/BuildCommand.php @@ -1,42 +1,37 @@ setName('domainator:build'); } /** + * Run the command. + * * @param InputInterface $input + * The input. * @param OutputInterface $output + * The output. */ public function execute(InputInterface $input, OutputInterface $output) { - $entityManager = $this->getContainer()->get('doctrine.orm.default_entity_manager'); - $eventDispatcher = $this->getContainer()->get('event_dispatcher'); - $task = $entityManager->getRepository(Task::class)->getNextTask(Task::TYPE_BUILD); - - if (!$task) { - return; - } - - $event = new BuildEvent($task); - $eventDispatcher->dispatch(BuildEvent::NAME, $event); + $this->runNextTask(Task::TYPE_BUILD); } } diff --git a/Command/DestroyCommand.php b/Command/DestroyCommand.php index 9721cfd..5bde9ac 100644 --- a/Command/DestroyCommand.php +++ b/Command/DestroyCommand.php @@ -1,42 +1,37 @@ setName('domainator:destroy'); } /** + * Run the command. + * * @param InputInterface $input + * The input. * @param OutputInterface $output + * The output. */ public function execute(InputInterface $input, OutputInterface $output) { - $entityManager = $this->getContainer()->get('doctrine.orm.default_entity_manager'); - $eventDispatcher = $this->getContainer()->get('event_dispatcher'); - $task = $entityManager->getRepository(Task::class)->getNextTask(Task::TYPE_DESTROY); - - if (!$task) { - return; - } - - $event = new DestroyEvent($task); - $eventDispatcher->dispatch(DestroyEvent::NAME, $event); + $this->runNextTask(Task::TYPE_DESTROY); } } diff --git a/Entity/Task.php b/Entity/Task.php index 2628717..c20a5ed 100644 --- a/Entity/Task.php +++ b/Entity/Task.php @@ -25,16 +25,19 @@ class Task use IdentifiableTrait; /** - * @var DateTime - * @ORM\Column(name="created", type="datetime", nullable=false) + * @var ApplicationEnvironment + * + * @ORM\ManyToOne(targetEntity="ApplicationEnvironment",inversedBy="tasks") + * @ORM\JoinColumn(referencedColumnName="id") */ - protected $created; + protected $applicationEnvironment; /** * @var string - * @ORM\Column(name="log", type="text", nullable=true) + * + * @ORM\Column(name="type",type="string") */ - protected $log; + protected $type; /** * @var string @@ -44,19 +47,16 @@ class Task protected $status; /** - * @var ApplicationEnvironment - * - * @ORM\ManyToOne(targetEntity="ApplicationEnvironment",inversedBy="tasks") - * @ORM\JoinColumn(referencedColumnName="id") + * @var DateTime + * @ORM\Column(name="created", type="datetime", nullable=false) */ - protected $applicationEnvironment; + protected $created; /** * @var string - * - * @ORM\Column(name="type",type="string") + * @ORM\Column(name="log", type="text", nullable=true) */ - protected $type; + protected $log; /** * Build constructor. @@ -68,51 +68,35 @@ public function __construct() } /** - * @return DateTime - */ - public function getCreated(): DateTime - { - return $this->created; - } - - /** - * Gets the log. - * - * @return string + * @return ApplicationEnvironment */ - public function getLog() + public function getApplicationEnvironment() { - return $this->log; + return $this->applicationEnvironment; } /** - * Sets the log. - * - * @param string $log - * - * @return $this + * @param ApplicationEnvironment $applicationEnvironment */ - public function setLog($log) + public function setApplicationEnvironment(ApplicationEnvironment $applicationEnvironment) { - $this->log = $log; - - return $this; + $this->applicationEnvironment = $applicationEnvironment; } /** - * @param ApplicationEnvironment $applicationEnvironment + * @return string */ - public function setApplicationEnvironment(ApplicationEnvironment $applicationEnvironment) + public function getType(): string { - $this->applicationEnvironment = $applicationEnvironment; + return $this->type; } /** - * @return ApplicationEnvironment + * @param string $type */ - public function getApplicationEnvironment() + public function setType(string $type) { - return $this->applicationEnvironment; + $this->type = $type; } /** @@ -132,18 +116,34 @@ public function setStatus(string $status) } /** + * @return DateTime + */ + public function getCreated(): DateTime + { + return $this->created; + } + + /** + * Gets the log. + * * @return string */ - public function getType(): string + public function getLog() { - return $this->type; + return $this->log; } /** - * @param string $type + * Sets the log. + * + * @param string $log + * + * @return $this */ - public function setType(string $type) + public function setLog($log) { - $this->type = $type; + $this->log = $log; + + return $this; } } diff --git a/Event/AbstractEvent.php b/Event/AbstractEvent.php index 1462be1..4efe518 100644 --- a/Event/AbstractEvent.php +++ b/Event/AbstractEvent.php @@ -1,20 +1,37 @@ task = $task; } + /** + * Get the task object. + * + * @return Task + */ public function getTask() { return $this->task; diff --git a/EventListener/BuildEventListener.php b/EventListener/BuildEventListener.php deleted file mode 100644 index 543e384..0000000 --- a/EventListener/BuildEventListener.php +++ /dev/null @@ -1,56 +0,0 @@ -taskLoggerService = $taskLoggerService; - $this->entityManager = $entityManager; - } - - public function onStart(BuildEvent $event) - { - $task = $event->getTask(); - $task->setStatus(Task::STATUS_IN_PROGRESS); - $this->entityManager->persist($task); - $this->entityManager->flush(); - $this->taskLoggerService->setTask($event->getTask()); - } - - public function onEnd(BuildEvent $event) - { - if ($event->getTask()->getStatus() == Task::STATUS_FAILED) { - $this->taskLoggerService->addLine('Build failed'); - return; - } - - $this->taskLoggerService->addLine('Build completed'); - $task = $event->getTask(); - $task->setStatus(Task::STATUS_PROCESSED); - $this->entityManager->persist($task); - $this->entityManager->flush(); - } -} diff --git a/EventListener/DestroyEventListener.php b/EventListener/DestroyEventListener.php deleted file mode 100644 index 56091aa..0000000 --- a/EventListener/DestroyEventListener.php +++ /dev/null @@ -1,58 +0,0 @@ -taskLoggerService = $taskLoggerService; - $this->entityManager = $entityManager; - } - - /** - * @param BuildEvent $event - */ - public function onStart(DestroyEvent $event) - { - $task = $event->getTask(); - $task->setStatus(Task::STATUS_IN_PROGRESS); - $this->entityManager->persist($task); - $this->entityManager->flush(); - $this->taskLoggerService->setTask($event->getTask()); - } - - /** - * @param BuildEvent $event - */ - public function onEnd(DestroyEvent $event) - { - $this->taskLoggerService->addLine('Destroy completed'); - $task = $event->getTask(); - $task->setStatus(Task::STATUS_PROCESSED); - $this->entityManager->persist($task); - $this->entityManager->flush(); - } -} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 8382d1c..f7c6bbd 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -5,15 +5,7 @@ services: tags: - { name: doctrine.event_listener, event: postPersist, connection: default } - { name: doctrine.event_listener, event: postRemove, connection: default } - DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService: - DigipolisGent\Domainator9k\CoreBundle\EventListener\BuildEventListener: - tags: - - { name: kernel.event_listener, event: domainator.build, method: onStart, priority: 99 } - - { name: kernel.event_listener, event: domainator.build, method: onEnd, priority: 1 } - DigipolisGent\Domainator9k\CoreBundle\EventListener\DestroyEventListener: - tags: - - { name: kernel.event_listener, event: domainator.destroy, method: onStart, priority: 99 } - - { name: kernel.event_listener, event: domainator.destroy, method: onEnd, priority: 1 } + DigipolisGent\Domainator9k\CoreBundle\Service\TaskService: DigipolisGent\Domainator9k\CoreBundle\Service\TemplateService: DigipolisGent\Domainator9k\CoreBundle\Service\TokenService: DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationEnvironmentFormType: diff --git a/Service/TaskLoggerService.php b/Service/TaskLoggerService.php deleted file mode 100644 index cbae01f..0000000 --- a/Service/TaskLoggerService.php +++ /dev/null @@ -1,55 +0,0 @@ -entityManager = $entityManager; - } - - /** - * @param Build $build - */ - public function setTask(Task $task) - { - $this->task = $task; - } - - /** - * @param string $line - */ - public function addLine(string $line) - { - $log = $this->task->getLog(); - $log .= $line . PHP_EOL; - - $this->task->setLog($log); - $this->entityManager->persist($this->task); - $this->entityManager->flush(); - } - - public function endTask() - { - $this->task->setStatus(Task::STATUS_FAILED); - $this->entityManager->persist($this->task); - $this->entityManager->flush(); - } -} diff --git a/Service/TaskService.php b/Service/TaskService.php new file mode 100644 index 0000000..b80f91e --- /dev/null +++ b/Service/TaskService.php @@ -0,0 +1,353 @@ +entityManager = $entityManager; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * Run a task. + * + * @param Task $task + * The task to run. + */ + public function run(Task $task) + { + if ($task->getStatus() !== Task::STATUS_NEW) { + throw new \InvalidArgumentException(sprintf('Task %s has already run and cannot be restarted.', $task->getId())); + } + + // Set the task in progress. + $task->setStatus(Task::STATUS_IN_PROGRESS); + $this->entityManager->persist($task); + $this->entityManager->flush(); + + // Create and dispatch the event. + $event = $this->createEvent($task); + $this->eventDispatcher->dispatch($event::NAME, $event); + $task = $event->getTask(); + + // Update the status. + if ($task->getStatus() === Task::STATUS_IN_PROGRESS) { + $status = Task::STATUS_PROCESSED; + if ($event->isPropagationStopped()) { + $status = Task::STATUS_FAILED; + } + + $task->setStatus($status); + } + + // Add a log message or simply persist any changes. + switch ($task->getStatus()) { + case Task::STATUS_PROCESSED: + $this->addInfoLogMessage($task, 'Task run completed.'); + break; + + case Task::STATUS_FAILED: + $this->addInfoLogMessage($task, 'Task run failed.'); + break; + + default: + $this->entityManager->persist($task); + $this->entityManager->flush(); + break; + } + } + + /** + * Run the next task of the specified type. + * + * @param string $type + * The task type to run. + */ + public function runNext(string $type) + { + $task = $this->entityManager + ->getRepository(Task::class) + ->getNextTask($type); + + if ($task) { + $this->run($task); + } + } + + /** + * Cancel a task. + * + * @param Task $task + * The task to cancel. + */ + public function cancel(Task $task) + { + if ($task->getStatus() !== Task::STATUS_NEW) { + throw new \InvalidArgumentException(sprintf('Task %s cannot be cancelled.', $task->getId())); + } + + $task->setStatus(Task::STATUS_CANCEL); + $this->addInfoLogMessage($task, 'Task run cancelled.'); + } + + /** + * Add a log header. + * + * @param Task $task + * The task object. + * @param string $header + * The log header. + * @param int $indent + * Number of levels to indent. + * @param bool $persist + * Persist the task to the database. + * + * @return self + */ + public function addLogHeader(Task $task, string $header, int $indent = 0, bool $persist = false) + { + if (($log = $task->getLog()) !== '') { + $log .= PHP_EOL; + } + + $header = trim($header); + $header = preg_replace('/[\r\n]+/', ' ', $header); + $header = '### ' . $header . ' ###'; + + $log .= $this->indentText($header, $indent); + + if ($persist) { + $this->entityManager->persist($task); + $this->entityManager->flush(); + } + + return $this; + } + + /** + * Add a log message. + * + * @param Task $task + * The task object. + * @param string $type + * The log type. + * @param string $message + * The log message. + * @param int $indent + * Number of levels to indent. + * @param bool $persist + * Persist the task to the database. + * + * @return self + */ + public function addLogMessage(Task $task, string $type, string $message, int $indent = 1, bool $persist = true) + { + if (($log = $task->getLog()) !== '') { + $log .= PHP_EOL; + } + + $message = trim($message); + $message = str_replace(["\r\n", "\r", "\n"], PHP_EOL, $message); + + if ($type !== self::LOG_TYPE_INFO) { + $message .= ' [' . $type . ']'; + } + + $log .= $this->indentText($message, $indent); + + $task->setLog($log); + + if ($persist) { + $this->entityManager->persist($task); + $this->entityManager->flush(); + } + + return $this; + } + + /** + * Add an "info" log message. + * + * @param Task $task + * The task object. + * @param string $message + * The log message. + * @param int $indent + * Number of levels to indent. + * @param bool $persist + * Persist the task to the database. + * + * @return self + */ + public function addInfoLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true) + { + return $this->addLogMessage($task, self::LOG_TYPE_INFO, $message, $indent, $persist); + } + + /** + * Add a "warning" log message. + * + * @param Task $task + * The task object. + * @param string $message + * The log message. + * @param int $indent + * Number of levels to indent. + * @param bool $persist + * Persist the task to the database. + * + * @return self + */ + public function addWarningLogMessage(Task $task, string $message, int $indent = 1, bool $persist = false) + { + return $this->addLogMessage($task, self::LOG_TYPE_WARNING, $message, $indent, $persist); + } + + /** + * Add an "error" log message. + * + * @param Task $task + * The task object. + * @param string $message + * The log message. + * @param int $indent + * Number of levels to indent. + * @param bool $persist + * Persist the task to the database. + * + * @return self + */ + public function addErrorLogMessage(Task $task, string $message, int $indent = 1, bool $persist = false) + { + return $this->addLogMessage($task, self::LOG_TYPE_ERROR, $message, $indent, $persist); + } + + /** + * Add a "success" log message. + * + * @param Task $task + * The task object. + * @param string $message + * The log message. + * @param int $indent + * Number of levels to indent. + * @param bool $persist + * Persist the task to the database. + * + * @return self + */ + public function addSuccessLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true) + { + return $this->addLogMessage($task, self::LOG_TYPE_SUCCESS, $message, $indent, $persist); + } + + /** + * Add a "failed" log message. + * + * @param Task $task + * The task object. + * @param string $message + * The log message. + * @param int $indent + * Number of levels to indent. + * @param bool $persist + * Persist the task to the database. + * + * @return self + */ + public function addFailedLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true) + { + return $this->addLogMessage($task, self::LOG_TYPE_FAILED, $message, $indent, $persist); + } + + /** + * Create an event object for a task. + * + * @param Task $task + * The task. + * + * @return AbstractEvent + * The event object. + */ + protected function createEvent(Task $task) { + switch ($task->getType()) { + case Task::TYPE_BUILD; + $class = BuildEvent::class; + break; + + case Task::TYPE_DESTROY: + $class = DestroyEvent::class; + break; + + default: + throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); + } + + return new $class($task); + } + + /** + * Indent a text. + * + * @param string $text + * The text to indent. + * @param int $indent + * Number of levels to indent. + * + * @return string + * The indented text. + */ + protected function indentText(string $text, int $indent) + { + if ($indent > 0) { + $text = preg_replace('/(^|[\r\n]+)/', '$1' . str_repeat('>', $indent) . str_repeat(' ', $indent), $text); + } + + return $text; + } + +} From b9739ce0f861f485e6a65457e63f51aaf6e3b834 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 11:18:34 +0200 Subject: [PATCH 039/104] WEBDOM-380: Fixed Code Climate issues. --- Service/TaskService.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index b80f91e..4f1dec8 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -61,7 +61,7 @@ public function __construct(EntityManagerInterface $entityManager, EventDispatch public function run(Task $task) { if ($task->getStatus() !== Task::STATUS_NEW) { - throw new \InvalidArgumentException(sprintf('Task %s has already run and cannot be restarted.', $task->getId())); + throw new \InvalidArgumentException(sprintf('Task "%s" cannot be restarted.', $task->getId())); } // Set the task in progress. @@ -313,9 +313,10 @@ public function addFailedLogMessage(Task $task, string $message, int $indent = 1 * @return AbstractEvent * The event object. */ - protected function createEvent(Task $task) { + protected function createEvent(Task $task) + { switch ($task->getType()) { - case Task::TYPE_BUILD; + case Task::TYPE_BUILD: $class = BuildEvent::class; break; @@ -349,5 +350,4 @@ protected function indentText(string $text, int $indent) return $text; } - } From 94910cbfeeedfc3876c84301e8b6e5d9649bd216 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 11:30:05 +0200 Subject: [PATCH 040/104] WEBDOM-380: Fixed log headers not being added. --- Service/TaskService.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Service/TaskService.php b/Service/TaskService.php index 4f1dec8..97bc323 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -160,6 +160,8 @@ public function addLogHeader(Task $task, string $header, int $indent = 0, bool $ $log .= $this->indentText($header, $indent); + $task->setLog($log); + if ($persist) { $this->entityManager->persist($task); $this->entityManager->flush(); From 7e207fba67eb10dac8f5e14dc3c737e2e458d8f5 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 11:36:29 +0200 Subject: [PATCH 041/104] WEBDOM-380: Fixed indentation on task result log. --- Service/TaskService.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index 97bc323..04387b2 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -87,11 +87,13 @@ public function run(Task $task) // Add a log message or simply persist any changes. switch ($task->getStatus()) { case Task::STATUS_PROCESSED: - $this->addInfoLogMessage($task, 'Task run completed.'); + $this->addLogMessage($task, '', '', 0); + $this->addSuccessLogMessage($task, 'Task run completed.', 0); break; case Task::STATUS_FAILED: - $this->addInfoLogMessage($task, 'Task run failed.'); + $this->addLogMessage($task, '', '', 0); + $this->addFailedLogMessage($task, 'Task run failed.', 0); break; default: @@ -195,7 +197,7 @@ public function addLogMessage(Task $task, string $type, string $message, int $in $message = trim($message); $message = str_replace(["\r\n", "\r", "\n"], PHP_EOL, $message); - if ($type !== self::LOG_TYPE_INFO) { + if ($type && $type !== self::LOG_TYPE_INFO) { $message .= ' [' . $type . ']'; } From b65f0c4fac253a92469d4dc3c028e0bc58bb14e0 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 14:05:47 +0200 Subject: [PATCH 042/104] WEBDOM-380: Added return types. --- Service/TaskService.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index 04387b2..b4f4a66 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -150,7 +150,7 @@ public function cancel(Task $task) * * @return self */ - public function addLogHeader(Task $task, string $header, int $indent = 0, bool $persist = false) + public function addLogHeader(Task $task, string $header, int $indent = 0, bool $persist = false): self { if (($log = $task->getLog()) !== '') { $log .= PHP_EOL; @@ -188,7 +188,7 @@ public function addLogHeader(Task $task, string $header, int $indent = 0, bool $ * * @return self */ - public function addLogMessage(Task $task, string $type, string $message, int $indent = 1, bool $persist = true) + public function addLogMessage(Task $task, string $type, string $message, int $indent = 1, bool $persist = true): self { if (($log = $task->getLog()) !== '') { $log .= PHP_EOL; @@ -227,7 +227,7 @@ public function addLogMessage(Task $task, string $type, string $message, int $in * * @return self */ - public function addInfoLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true) + public function addInfoLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true): self { return $this->addLogMessage($task, self::LOG_TYPE_INFO, $message, $indent, $persist); } @@ -246,7 +246,7 @@ public function addInfoLogMessage(Task $task, string $message, int $indent = 1, * * @return self */ - public function addWarningLogMessage(Task $task, string $message, int $indent = 1, bool $persist = false) + public function addWarningLogMessage(Task $task, string $message, int $indent = 1, bool $persist = false): self { return $this->addLogMessage($task, self::LOG_TYPE_WARNING, $message, $indent, $persist); } @@ -265,7 +265,7 @@ public function addWarningLogMessage(Task $task, string $message, int $indent = * * @return self */ - public function addErrorLogMessage(Task $task, string $message, int $indent = 1, bool $persist = false) + public function addErrorLogMessage(Task $task, string $message, int $indent = 1, bool $persist = false): self { return $this->addLogMessage($task, self::LOG_TYPE_ERROR, $message, $indent, $persist); } @@ -284,7 +284,7 @@ public function addErrorLogMessage(Task $task, string $message, int $indent = 1, * * @return self */ - public function addSuccessLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true) + public function addSuccessLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true): self { return $this->addLogMessage($task, self::LOG_TYPE_SUCCESS, $message, $indent, $persist); } @@ -303,7 +303,7 @@ public function addSuccessLogMessage(Task $task, string $message, int $indent = * * @return self */ - public function addFailedLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true) + public function addFailedLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true): self { return $this->addLogMessage($task, self::LOG_TYPE_FAILED, $message, $indent, $persist); } @@ -317,7 +317,7 @@ public function addFailedLogMessage(Task $task, string $message, int $indent = 1 * @return AbstractEvent * The event object. */ - protected function createEvent(Task $task) + protected function createEvent(Task $task): AbstractEvent { switch ($task->getType()) { case Task::TYPE_BUILD: From ca02c72eb06988fb12f262a11f02198565af20e7 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 14:06:07 +0200 Subject: [PATCH 043/104] WEBDOM-380: Removed old tests. --- .../EventListener/BuildEventListenerTest.php | 62 ------------------ .../DestroyEventListenerTest.php | 64 ------------------- Tests/Service/TaskLoggerServiceTest.php | 39 ----------- 3 files changed, 165 deletions(-) delete mode 100644 Tests/EventListener/BuildEventListenerTest.php delete mode 100644 Tests/EventListener/DestroyEventListenerTest.php delete mode 100644 Tests/Service/TaskLoggerServiceTest.php diff --git a/Tests/EventListener/BuildEventListenerTest.php b/Tests/EventListener/BuildEventListenerTest.php deleted file mode 100644 index 5adc7b0..0000000 --- a/Tests/EventListener/BuildEventListenerTest.php +++ /dev/null @@ -1,62 +0,0 @@ -getTaskLoggerServiceMock(); - $taskLoggerService - ->expects($this->at(0)) - ->method('setTask'); - - $entityManager = $this->getEntityManagerMock(); - - $buildEventListener = new BuildEventListener($taskLoggerService,$entityManager); - $buildEventListener->onStart(new BuildEvent(new Task())); - } - - public function testOnEnd(){ - $taskLoggerService = $this->getTaskLoggerServiceMock(); - $entityManager = $this->getEntityManagerMock(); - - $buildEventListener = new BuildEventListener($taskLoggerService,$entityManager); - $buildEventListener->onEnd(new BuildEvent(new Task())); - } - - public function getTaskLoggerServiceMock(){ - $mock = $this - ->getMockBuilder(TaskLoggerService::class) - ->disableOriginalConstructor() - ->getMock(); - - return $mock; - } - - public function getEntityManagerMock(){ - $mock = $this - ->getMockBuilder(EntityManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $mock - ->expects($this->at(0)) - ->method('persist'); - - $mock - ->expects($this->at(1)) - ->method('flush'); - - return $mock; - } - -} \ No newline at end of file diff --git a/Tests/EventListener/DestroyEventListenerTest.php b/Tests/EventListener/DestroyEventListenerTest.php deleted file mode 100644 index 5985b4e..0000000 --- a/Tests/EventListener/DestroyEventListenerTest.php +++ /dev/null @@ -1,64 +0,0 @@ -getTaskLoggerServiceMock(); - $taskLoggerService - ->expects($this->at(0)) - ->method('setTask'); - - $entityManager = $this->getEntityManagerMock(); - - $buildEventListener = new DestroyEventListener($taskLoggerService,$entityManager); - $buildEventListener->onStart(new DestroyEvent(new Task())); - } - - public function testOnEnd(){ - $taskLoggerService = $this->getTaskLoggerServiceMock(); - $entityManager = $this->getEntityManagerMock(); - - $buildEventListener = new DestroyEventListener($taskLoggerService,$entityManager); - $buildEventListener->onEnd(new DestroyEvent(new Task())); - } - - public function getTaskLoggerServiceMock(){ - $mock = $this - ->getMockBuilder(TaskLoggerService::class) - ->disableOriginalConstructor() - ->getMock(); - - return $mock; - } - - public function getEntityManagerMock(){ - $mock = $this - ->getMockBuilder(EntityManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $mock - ->expects($this->at(0)) - ->method('persist'); - - $mock - ->expects($this->at(1)) - ->method('flush'); - - return $mock; - } - -} \ No newline at end of file diff --git a/Tests/Service/TaskLoggerServiceTest.php b/Tests/Service/TaskLoggerServiceTest.php deleted file mode 100644 index 0d24d37..0000000 --- a/Tests/Service/TaskLoggerServiceTest.php +++ /dev/null @@ -1,39 +0,0 @@ -getEntityManagerMock(); - $loggerService = new TaskLoggerService($entityManager); - $task = new Task(); - $loggerService->setTask($task); - $loggerService->addLine('New log line'); - } - - private function getEntityManagerMock(){ - $mock = $this - ->getMockBuilder(EntityManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $mock - ->expects($this->at(0)) - ->method('persist'); - - $mock - ->expects($this->at(0)) - ->method('flush'); - - return $mock; - } - -} From 2149cc1df05170f09f1f08f9eda4ec0406d20aba Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 15:40:38 +0200 Subject: [PATCH 044/104] WEBDOM-380: Removed command tests. --- Tests/Command/AbstractCommandTest.php | 102 -------------------------- Tests/Command/BuildCommandTest.php | 43 ----------- Tests/Command/DestroyCommandTest.php | 43 ----------- 3 files changed, 188 deletions(-) delete mode 100644 Tests/Command/AbstractCommandTest.php delete mode 100644 Tests/Command/BuildCommandTest.php delete mode 100644 Tests/Command/DestroyCommandTest.php diff --git a/Tests/Command/AbstractCommandTest.php b/Tests/Command/AbstractCommandTest.php deleted file mode 100644 index e62d0d1..0000000 --- a/Tests/Command/AbstractCommandTest.php +++ /dev/null @@ -1,102 +0,0 @@ -getMockBuilder(ContainerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $mock - ->expects($this->at(0)) - ->method('get') - ->with($this->equalTo('doctrine.orm.default_entity_manager')) - ->willReturn($entityManager); - - $mock - ->expects($this->at(1)) - ->method('get') - ->with($this->equalTo('event_dispatcher')) - ->willReturn($eventDispatcher); - - return $mock; - } - - protected function getInputInterfaceMock() - { - $mock = $this - ->getMockBuilder(InputInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - return $mock; - } - - protected function getOutputInterfaceMock() - { - $mock = $this - ->getMockBuilder(OutputInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - return $mock; - } - - protected function getEntityManagerMock($taskRepository) - { - $mock = $this - ->getMockBuilder(EntityManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $mock - ->expects($this->at(0)) - ->method('getRepository') - ->with($this->equalTo(Task::class)) - ->willReturn($taskRepository); - - return $mock; - } - - protected function getEventDispatcherMock() - { - $mock = $this - ->getMockBuilder(EventDispatcher::class) - ->disableOriginalConstructor() - ->getMock(); - - return $mock; - } - - protected function getTaskRepositoryMock($type, $task) - { - $mock = $this - ->getMockBuilder(TaskRepository::class) - ->disableOriginalConstructor() - ->getMock(); - - $mock - ->expects($this->at(0)) - ->method('getNextTask') - ->with($this->equalTo($type)) - ->willReturn($task); - - return $mock; - } -} - diff --git a/Tests/Command/BuildCommandTest.php b/Tests/Command/BuildCommandTest.php deleted file mode 100644 index 2e9a943..0000000 --- a/Tests/Command/BuildCommandTest.php +++ /dev/null @@ -1,43 +0,0 @@ -getTaskRepositoryMock(Task::TYPE_BUILD, null); - - $entityManager = $this->getEntityManagerMock($taskRepository); - $eventDispatcher = $this->getEventDispatcherMock(); - - $container = $this->getContainerMock($entityManager, $eventDispatcher); - $inputInterface = $this->getInputInterfaceMock(); - $outputInterface = $this->getOutputInterfaceMock(); - - $buildCommand = new BuildCommand(); - $buildCommand->setContainer($container); - $buildCommand->execute($inputInterface, $outputInterface); - } - - public function testExecuteWithTask() - { - $taskRepository = $this->getTaskRepositoryMock(Task::TYPE_BUILD, new Task()); - - $entityManager = $this->getEntityManagerMock($taskRepository); - $eventDispatcher = $this->getEventDispatcherMock(); - - $container = $this->getContainerMock($entityManager, $eventDispatcher); - $inputInterface = $this->getInputInterfaceMock(); - $outputInterface = $this->getOutputInterfaceMock(); - - $buildCommand = new BuildCommand(); - $buildCommand->setContainer($container); - $buildCommand->execute($inputInterface, $outputInterface); - } -} diff --git a/Tests/Command/DestroyCommandTest.php b/Tests/Command/DestroyCommandTest.php deleted file mode 100644 index e1f5f78..0000000 --- a/Tests/Command/DestroyCommandTest.php +++ /dev/null @@ -1,43 +0,0 @@ -getTaskRepositoryMock(Task::TYPE_DESTROY, null); - - $entityManager = $this->getEntityManagerMock($taskRepository); - $eventDispatcher = $this->getEventDispatcherMock(); - - $container = $this->getContainerMock($entityManager, $eventDispatcher); - $inputInterface = $this->getInputInterfaceMock(); - $outputInterface = $this->getOutputInterfaceMock(); - - $buildCommand = new DestroyCommand(); - $buildCommand->setContainer($container); - $buildCommand->execute($inputInterface, $outputInterface); - } - - public function testExecuteWithTask() - { - $taskRepository = $this->getTaskRepositoryMock(Task::TYPE_DESTROY, new Task()); - - $entityManager = $this->getEntityManagerMock($taskRepository); - $eventDispatcher = $this->getEventDispatcherMock(); - - $container = $this->getContainerMock($entityManager, $eventDispatcher); - $inputInterface = $this->getInputInterfaceMock(); - $outputInterface = $this->getOutputInterfaceMock(); - - $buildCommand = new DestroyCommand(); - $buildCommand->setContainer($container); - $buildCommand->execute($inputInterface, $outputInterface); - } -} From 76de6c50f5b3b1834f9fd7ae662ecc6448614243 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 16:29:52 +0200 Subject: [PATCH 045/104] WEBDOM-380: Fixed unneeded newline in the beginning of the log. --- Service/TaskService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index b4f4a66..f9f8973 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -152,7 +152,7 @@ public function cancel(Task $task) */ public function addLogHeader(Task $task, string $header, int $indent = 0, bool $persist = false): self { - if (($log = $task->getLog()) !== '') { + if ($log = $task->getLog()) { $log .= PHP_EOL; } @@ -190,7 +190,7 @@ public function addLogHeader(Task $task, string $header, int $indent = 0, bool $ */ public function addLogMessage(Task $task, string $type, string $message, int $indent = 1, bool $persist = true): self { - if (($log = $task->getLog()) !== '') { + if ($log = $task->getLog()) { $log .= PHP_EOL; } From 6b00c0e983635d5a4d8959f626f0ca82a78ef87c Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Thu, 24 May 2018 17:05:55 +0200 Subject: [PATCH 046/104] WEBDOM-380: Changed indentation to tabs. --- Service/TaskService.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index f9f8973..d40ccca 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -348,10 +348,6 @@ protected function createEvent(Task $task): AbstractEvent */ protected function indentText(string $text, int $indent) { - if ($indent > 0) { - $text = preg_replace('/(^|[\r\n]+)/', '$1' . str_repeat('>', $indent) . str_repeat(' ', $indent), $text); - } - - return $text; + return preg_replace('/(^|[\r\n]+\t*)/', '$1' . str_repeat("\t", $indent), $text); } } From aa54c6683cf791de41d6b0744f84c42be133b2d8 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 25 May 2018 16:23:03 +0200 Subject: [PATCH 047/104] WEBDOM-380: Convert tabs to spaces when indenting log text. --- Service/TaskService.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index d40ccca..3f7cc39 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -348,6 +348,18 @@ protected function createEvent(Task $task): AbstractEvent */ protected function indentText(string $text, int $indent) { - return preg_replace('/(^|[\r\n]+\t*)/', '$1' . str_repeat("\t", $indent), $text); + return preg_replace_callback('/(^|[\r\n]+)(\t+)?/', function($matches) { + $suffix = ''; + + if ($indent) { + $suffix .= str_repeat("\t", $indent); + } + + if (isset($matches[2])) { + $suffix = str_repeat(' ', strlen($matches[2])); + } + + return $matches[1] . $suffix; + }, $text); } } From 13772bcbf571743e9b9b56a28907e855c978905b Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 25 May 2018 16:34:23 +0200 Subject: [PATCH 048/104] WEBDOM-380: Fixed missing indentation. --- Service/TaskService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index 3f7cc39..69eb7fd 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -356,7 +356,7 @@ protected function indentText(string $text, int $indent) } if (isset($matches[2])) { - $suffix = str_repeat(' ', strlen($matches[2])); + $suffix .= str_repeat(' ', strlen($matches[2])); } return $matches[1] . $suffix; From 573e744459f3dedf6fe834bd34dc78f047d46eac Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 25 May 2018 16:45:03 +0200 Subject: [PATCH 049/104] WEBDOM-380: Fixed missing indentation. --- Service/TaskService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Service/TaskService.php b/Service/TaskService.php index 69eb7fd..abaa644 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -348,7 +348,7 @@ protected function createEvent(Task $task): AbstractEvent */ protected function indentText(string $text, int $indent) { - return preg_replace_callback('/(^|[\r\n]+)(\t+)?/', function($matches) { + return preg_replace_callback('/(^|[\r\n]+)(\t+)?/', function($matches) use ($indent) { $suffix = ''; if ($indent) { From 32321038399cecbde895116b73a2d400324b8a89 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 1 Jun 2018 16:57:05 +0200 Subject: [PATCH 050/104] Changed the minimum length or an application name to 2. --- Entity/AbstractApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 125f955..3bbaf54 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -32,7 +32,7 @@ abstract class AbstractApplication implements TemplateInterface * * @ORM\Column(name="name", type="string", nullable=false) * @Assert\NotBlank() - * @Assert\Length(min="3", max="255") + * @Assert\Length(min="2", max="255") */ protected $name; From 39dec076981bec6a97582412d8d0057a3e896095 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 15:57:26 +0200 Subject: [PATCH 051/104] Refactor templates to be more generic. --- Entity/AbstractApplication.php | 20 ++-- Entity/ApplicationEnvironment.php | 40 ++----- Entity/Environment.php | 23 ++-- Entity/TemplateInterface.php | 2 +- Entity/Traits/TemplateImplementationTrait.php | 112 ++++++++++++++++++ 5 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 Entity/Traits/TemplateImplementationTrait.php diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 125f955..2934537 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -4,6 +4,7 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Entity; use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\IdentifiableTrait; +use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; use DigipolisGent\SettingBundle\Entity\Traits\SettingImplementationTrait; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; @@ -26,6 +27,7 @@ abstract class AbstractApplication implements TemplateInterface use SettingImplementationTrait; use IdentifiableTrait; + use TemplateImplementationTrait; /** * @var string @@ -95,7 +97,7 @@ public function __construct() /** * @return string */ - public function getName() + public function getName(): string { return $this->name; } @@ -111,7 +113,7 @@ public function setName(string $name) /** * @return string */ - public function getNameCanonical() + public function getNameCanonical(): string { $name = strtolower(preg_replace("/[^a-zA-Z0-9]+/", "", $this->getName())); return substr($name, 0, 14); @@ -120,7 +122,7 @@ public function getNameCanonical() /** * @return string */ - public function getGitRepo() + public function getGitRepo(): string { return $this->gitRepo; } @@ -169,7 +171,7 @@ public function removeApplicationEnvironment(ApplicationEnvironment $application /** * @return ArrayCollection */ - public function getApplicationEnvironments() + public function getApplicationEnvironments(): ArrayCollection { return $this->applicationEnvironments; } @@ -178,7 +180,7 @@ public function getApplicationEnvironments() * @param $name * @return mixed|null */ - public function getApplicationEnvironmentByEnvironmentName(string $name) + public function getApplicationEnvironmentByEnvironmentName(string $name): ApplicationEnvironment { foreach ($this->applicationEnvironments as $applicationEnvironment) { if ($applicationEnvironment->getEnvironment()->getName() == $name) { @@ -186,18 +188,16 @@ public function getApplicationEnvironmentByEnvironmentName(string $name) } } - return ''; + return null; } /** * @return array */ - public static function getTemplateReplacements(): array + public static function additionalTemplateReplacements(): array { + // Backward compatibility. return [ - 'name()' => 'getName()', - 'nameCanonical()' => 'getNameCanonical()', - 'gitRepo()' => 'getGitRepo()', 'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', ]; } diff --git a/Entity/ApplicationEnvironment.php b/Entity/ApplicationEnvironment.php index ef1dc7f..11d10a4 100644 --- a/Entity/ApplicationEnvironment.php +++ b/Entity/ApplicationEnvironment.php @@ -3,6 +3,7 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Entity; use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\IdentifiableTrait; +use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; use DigipolisGent\SettingBundle\Entity\Traits\SettingImplementationTrait; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; @@ -17,6 +18,7 @@ class ApplicationEnvironment implements TemplateInterface use SettingImplementationTrait; use IdentifiableTrait; + use TemplateImplementationTrait; /** * @var ArrayCollection @@ -90,28 +92,10 @@ public static function getSettingImplementationName() return 'application_environment'; } - /** - * @return array - */ - public static function getTemplateReplacements(): array - { - return [ - 'serverIps()' => 'getServerIps()', - 'workerServerIp()' => 'getWorkerServerIp()', - 'environmentName()' => 'getEnvironment().getName()', - 'config(key)' => 'getConfig(key)', - 'databaseName()' => 'getDatabaseName()', - 'databaseUser()' => 'getDatabaseUser()', - 'databasePassword()' => 'getDatabasePassword()', - 'gitRef()' => 'getGitRef()', - 'domain()' => 'getDomain()', - ]; - } - /** * @return AbstractApplication */ - public function getApplication() + public function getApplication(): ?AbstractApplication { return $this->application; } @@ -127,7 +111,7 @@ public function setApplication(AbstractApplication $application = null) /** * @return string */ - public function getDatabaseName() + public function getDatabaseName(): ?string { return $this->databaseName; } @@ -143,7 +127,7 @@ public function setDatabaseName(string $databaseName = null) /** * @return string */ - public function getEnvironmentName() + public function getEnvironmentName(): ?string { return $this->getEnvironment()->getName(); } @@ -151,7 +135,7 @@ public function getEnvironmentName() /** * @return Environment */ - public function getEnvironment() + public function getEnvironment(): Environment { return $this->environment; } @@ -167,7 +151,7 @@ public function setEnvironment(Environment $environment) /** * @return string */ - public function getDatabaseUser() + public function getDatabaseUser(): ?string { return $this->databaseUser; } @@ -183,7 +167,7 @@ public function setDatabaseUser(string $databaseUser = null) /** * @return string */ - public function getDatabasePassword() + public function getDatabasePassword(): ?string { return $this->databasePassword; } @@ -199,7 +183,7 @@ public function setDatabasePassword(string $databasePassword = null) /** * @return string */ - public function getGitRef() + public function getGitRef(): ?string { return $this->gitRef; } @@ -215,7 +199,7 @@ public function setGitRef(string $gitRef = null) /** * @return ArrayCollection */ - public function getTasks() + public function getTasks(): ArrayCollection { return $this->tasks; } @@ -223,7 +207,7 @@ public function getTasks() /** * @return string */ - public function getDomain() + public function getDomain(): ?string { return $this->domain; } @@ -266,7 +250,7 @@ public function getWorkerServerIp(): string return end($servers)->getHost(); } - public function getConfig($key) + public function getConfig(string $key): ?string { foreach ($this->getSettingDataValues() as $settingDataValue) { if ($settingDataValue->getSettingDataType()->getKey() == $key) { diff --git a/Entity/Environment.php b/Entity/Environment.php index 769dca7..036a24a 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -3,6 +3,7 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Entity; use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\IdentifiableTrait; +use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; use DigipolisGent\SettingBundle\Entity\Traits\SettingImplementationTrait; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; @@ -19,6 +20,7 @@ class Environment implements TemplateInterface use SettingImplementationTrait; use IdentifiableTrait; + use TemplateImplementationTrait; /** * @var string @@ -98,7 +100,7 @@ public static function getSettingImplementationName() * * @return string */ - public function getName() + public function getName(): ?string { return $this->name; } @@ -156,7 +158,7 @@ public function addApplicationEnvironment(ApplicationEnvironment $applicationEnv /** * @return ArrayCollection */ - public function getApplicationEnvironments() + public function getApplicationEnvironments(): ArrayCollection { return $this->applicationEnvironments; } @@ -172,7 +174,7 @@ public function addApplicationTypeEnvironment(ApplicationTypeEnvironment $applic /** * @return ArrayCollection */ - public function getApplicationTypeEnvironments() + public function getApplicationTypeEnvironments(): ArrayCollection { return $this->applicationTypeEnvironments; } @@ -189,7 +191,7 @@ public function addVirtualServer(VirtualServer $virtualServer) /** * @return ArrayCollection */ - public function getVirtualServers() + public function getVirtualServers(): ArrayCollection { return $this->virtualServers; } @@ -210,7 +212,7 @@ public function setGitRef(string $gitRef) $this->gitRef = $gitRef; } - public function getConfig($key) + public function getConfig(string $key): ?string { foreach ($this->getSettingDataValues() as $settingDataValue) { if ($settingDataValue->getSettingDataType()->getKey() == $key) { @@ -221,17 +223,6 @@ public function getConfig($key) return ''; } - /** - * @return array - */ - public static function getTemplateReplacements(): array - { - return [ - 'config(key)' => 'getConfig(key)', - 'gitRef()' => 'getGitRef()', - ]; - } - /** * @return int */ diff --git a/Entity/TemplateInterface.php b/Entity/TemplateInterface.php index 083b665..64347a3 100644 --- a/Entity/TemplateInterface.php +++ b/Entity/TemplateInterface.php @@ -9,5 +9,5 @@ */ interface TemplateInterface { - public static function getTemplateReplacements(): array; + public static function getTemplateReplacements(int $maxDepth = 3, array $skip = []): array; } diff --git a/Entity/Traits/TemplateImplementationTrait.php b/Entity/Traits/TemplateImplementationTrait.php new file mode 100644 index 0000000..a745d74 --- /dev/null +++ b/Entity/Traits/TemplateImplementationTrait.php @@ -0,0 +1,112 @@ +hasMethod('additionalTemplateReplacements')) { + $replacements += static::additionalTemplateReplacements(); + } + return $replacements; + } + + /** + * Get relevant methods for template replacements. + * @param \ReflectionClass $class + * @return \ReflectionMethod[] + */ + protected static function getRelevantMethods(ReflectionClass $class) + { + // Get all public methods. + $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); + $relevantMethods = []; + foreach ($methods as $method) { + // That are not abstract or static. + if ($method->isAbstract() || $method->isStatic()) { + continue; + } + // That start with -but are not equal to- 'get'. + $name = $method->getName(); + if ($name === 'get' || substr($name, 0, 3) !== 'get') { + continue; + } + // That have a return type. + if (!$method->hasReturnType()) { + continue; + } + $returnType = $method->getReturnType(); + // Whose return type is scalar or implements TemplateInterface and + // is different from the current class (prevent loops). + if (!$returnType->isBuiltin() && (!class_exists($returnType) || !is_a((string)$returnType, TemplateInterface::class, true))) { + continue; + } + // Whose parameters are scalar (or non existant). + foreach ($method->getParameters() as $parameter) { + $parameterType = $parameter->getType(); + if (!is_null($parameterType) && !$parameterType->isBuiltin()) { + continue 2; + } + } + $relevantMethods[$method->getName()] = $method; + } + return $relevantMethods; + } + + protected static function getTemplateReplacementsForMethod(ReflectionMethod $method, int $maxDepth, array $skip) + { + $returnType = $method->getReturnType(); + $parameters = []; + foreach ($method->getParameters() as $parameter) { + $parameters[] = $parameter->getName(); + } + $replacementParameters = implode(',', $parameters); + $replacementCallback = $method->getName() . '(' . $replacementParameters . ')'; + if ($returnType->isBuiltin()) { + $template = lcfirst(substr($method->getName(), 3)) . '(' . $replacementParameters . ')'; + return [$template => $replacementCallback]; + } + if ($maxDepth < 0 || in_array((string)$returnType, $skip)) { + return []; + } + $replacements = []; + $subs = call_user_func(array((string)$returnType, 'getTemplateReplacements'), $maxDepth, $skip); + foreach ($subs as $subTemplate => $replacementSubCallback) { + $template = lcfirst( + str_replace('Abstract', '', ReflectionClass::createFromName((string)$returnType)->getShortName()) + ) + . ucfirst( + str_replace( + ['(,', ',)'], + ['(', ')'], + preg_replace( + '/\((.*)\)/', + '(' . $replacementParameters . ',$1)', + $subTemplate + ) + ) + ); + $replacements[$template] = $replacementCallback . '.' . $replacementSubCallback; + } + return $replacements; + } +} From 4ef4ce5e96ebde950f85973dcbc35138256a654b Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 15:59:22 +0200 Subject: [PATCH 052/104] Added a template helper twig extension. --- Resources/config/services.yml | 2 + Resources/public/js/templatehelper.js | 81 ++++++++++++++++++++ Resources/views/Template/templatehelper.twig | 12 +++ Twig/TemplateHelpExtension.php | 46 +++++++++++ 4 files changed, 141 insertions(+) create mode 100644 Resources/public/js/templatehelper.js create mode 100644 Resources/views/Template/templatehelper.twig create mode 100644 Twig/TemplateHelpExtension.php diff --git a/Resources/config/services.yml b/Resources/config/services.yml index f7c6bbd..be8bfca 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -18,3 +18,5 @@ services: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationTypeEnvironmentFormType: tags: [form.type] + DigipolisGent\Domainator9k\CoreBundle\Twig\TemplateHelpExtension: + tags: [twig.extension] diff --git a/Resources/public/js/templatehelper.js b/Resources/public/js/templatehelper.js new file mode 100644 index 0000000..4c918a7 --- /dev/null +++ b/Resources/public/js/templatehelper.js @@ -0,0 +1,81 @@ +(function (document, window) { + window.templateHelpers = {}; + document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll('dialog[data-template-helper-textarea]').forEach(function (dialog) { + if (!dialog.dataset.templateHelperProcessed) { + var selector = dialog.dataset.templateHelperTextarea; + window.templateHelpers[selector] = new TemplateHelper(dialog); + dialog.dataset.templateHelperProcessed = true; + } + }); + }); + + function TemplateHelper(dialog) { + var self = this; + self.dialog = dialog; + self.textareas = new Array(); + self.openDialogLinks = {} + document.querySelectorAll(dialog.dataset.templateHelperTextarea).forEach(function (textarea) { + self.registerTextarea(textarea); + }); + self.bindDialogClose(); + self.bindTemplateLinks(); + } + + TemplateHelper.prototype.registerTextarea = function (textarea) { + var self = this; + self.textareas.push(textarea); + self.activeTextarea = textarea; + self.createDialogLink(textarea); + }; + + TemplateHelper.prototype.bindDialogClose = function () { + var self = this; + self.dialog.querySelectorAll('.close-template-dialog')[0].addEventListener('click', function (e) { + e.preventDefault(); + self.closeDialog(); + }); + }; + + TemplateHelper.prototype.openDialog = function () { + var self = this; + self.dialog.showModal(); + }; + + TemplateHelper.prototype.closeDialog = function () { + var self = this; + self.dialog.close(); + }; + + TemplateHelper.prototype.createDialogLink = function (textarea) { + var self = this; + var link = document.createElement('a'); + link.href = '#'; + link.classList.add('template-helper-dialog-link'); + link.appendChild(document.createTextNode('Insert template value')); + link.addEventListener('click', function (e) { + e.preventDefault(); + self.activeTextarea = textarea; + self.openDialog(); + }); + textarea.parentNode.insertBefore(link, textarea.nextSibling); + self.openDialogLinks[textarea.name] = link; + }; + + TemplateHelper.prototype.bindTemplateLinks = function() { + var self = this; + self.dialog.querySelectorAll('a[data-template-value]').forEach(function (link) { + link.addEventListener('click', function(e) { + e.preventDefault(); + self.insertTemplate(link.dataset.templateValue); + }); + }); + }; + + TemplateHelper.prototype.insertTemplate = function (template) { + var self = this; + self.activeTextarea.value = self.activeTextarea.value + template; + var event = new Event('change'); + self.activeTextarea.dispatchEvent(event); + }; +})(document, window); diff --git a/Resources/views/Template/templatehelper.twig b/Resources/views/Template/templatehelper.twig new file mode 100644 index 0000000..3fa2f53 --- /dev/null +++ b/Resources/views/Template/templatehelper.twig @@ -0,0 +1,12 @@ + + + {% for key,template_items in templates %} +
+ {{ key }} + {% for template in template_items %} + + {% endfor %} +
+ {% endfor %} + +
diff --git a/Twig/TemplateHelpExtension.php b/Twig/TemplateHelpExtension.php new file mode 100644 index 0000000..b2359a4 --- /dev/null +++ b/Twig/TemplateHelpExtension.php @@ -0,0 +1,46 @@ +tokenService = $tokenService; + } + + public function getFunctions() + { + return array( + new TwigFunction( + 'template_help', + array($this, 'templateHelp'), + array('needs_environment' => true, 'is_safe' => array('html')) + ), + ); + } + + public function templateHelp(Environment $environment, array $classes, $textarea) + { + + $templates = [ + 'token' => array_keys($this->tokenService->getTemplateReplacements()), + ]; + foreach ($classes as $key => $class) { + if (!is_a($class, TemplateInterface::class, true)) { + new RuntimeError(sprintf('Class %s does not implement %s.', $class, TemplateInterface::class)); + } + $templates[$key] = array_keys(call_user_func([$class, 'getTemplateReplacements'])); + } + + return $environment->render('@DigipolisGentDomainator9kCore/Template/templatehelper.twig', ['templates' => $templates, 'textarea' => $textarea]); + } +} From c6fd9094bf070bb13c19a85e64bcbe5a06307c14 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 17:17:36 +0200 Subject: [PATCH 053/104] Fix tests. --- Entity/AbstractApplication.php | 6 +++--- Entity/ApplicationEnvironment.php | 2 +- Entity/Traits/TemplateImplementationTrait.php | 5 +++++ Tests/Entity/AbstractApplicationTest.php | 14 +++++++++++++- Tests/Entity/ApplicationEnvironmentTest.php | 9 ++++++++- Tests/Fixtures/Entity/Foo.php | 18 +++++++++--------- Tests/Fixtures/Entity/Qux.php | 14 ++++---------- 7 files changed, 43 insertions(+), 25 deletions(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index 2934537..e82959a 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -97,7 +97,7 @@ public function __construct() /** * @return string */ - public function getName(): string + public function getName(): ?string { return $this->name; } @@ -113,7 +113,7 @@ public function setName(string $name) /** * @return string */ - public function getNameCanonical(): string + public function getNameCanonical(): ?string { $name = strtolower(preg_replace("/[^a-zA-Z0-9]+/", "", $this->getName())); return substr($name, 0, 14); @@ -180,7 +180,7 @@ public function getApplicationEnvironments(): ArrayCollection * @param $name * @return mixed|null */ - public function getApplicationEnvironmentByEnvironmentName(string $name): ApplicationEnvironment + public function getApplicationEnvironmentByEnvironmentName(string $name): ?ApplicationEnvironment { foreach ($this->applicationEnvironments as $applicationEnvironment) { if ($applicationEnvironment->getEnvironment()->getName() == $name) { diff --git a/Entity/ApplicationEnvironment.php b/Entity/ApplicationEnvironment.php index 11d10a4..f443151 100644 --- a/Entity/ApplicationEnvironment.php +++ b/Entity/ApplicationEnvironment.php @@ -135,7 +135,7 @@ public function getEnvironmentName(): ?string /** * @return Environment */ - public function getEnvironment(): Environment + public function getEnvironment(): ?Environment { return $this->environment; } diff --git a/Entity/Traits/TemplateImplementationTrait.php b/Entity/Traits/TemplateImplementationTrait.php index a745d74..bbbbe60 100644 --- a/Entity/Traits/TemplateImplementationTrait.php +++ b/Entity/Traits/TemplateImplementationTrait.php @@ -88,6 +88,11 @@ protected static function getTemplateReplacementsForMethod(ReflectionMethod $met if ($maxDepth < 0 || in_array((string)$returnType, $skip)) { return []; } + foreach ($skip as $skipClass) { + if (is_a($skipClass, (string) $returnType, true)) { + return []; + } + } $replacements = []; $subs = call_user_func(array((string)$returnType, 'getTemplateReplacements'), $maxDepth, $skip); foreach ($subs as $subTemplate => $replacementSubCallback) { diff --git a/Tests/Entity/AbstractApplicationTest.php b/Tests/Entity/AbstractApplicationTest.php index ae25803..56aa8c4 100644 --- a/Tests/Entity/AbstractApplicationTest.php +++ b/Tests/Entity/AbstractApplicationTest.php @@ -17,8 +17,20 @@ public function testGetTemplateReplacements() $expected = [ 'name()' => 'getName()', 'nameCanonical()' => 'getNameCanonical()', - 'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', 'gitRepo()' => 'getGitRepo()', + 'applicationEnvironmentDatabaseName(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getDatabaseName()', + 'applicationEnvironmentEnvironmentName(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getEnvironmentName()', + 'applicationEnvironmentEnvironmentGitRef(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getEnvironment().getGitRef()', + 'applicationEnvironmentEnvironmentConfig(name,key)' => 'getApplicationEnvironmentByEnvironmentName(name).getEnvironment().getConfig(key)', + 'applicationEnvironmentEnvironmentPriority(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getEnvironment().getPriority()', + 'applicationEnvironmentDatabaseUser(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getDatabaseUser()', + 'applicationEnvironmentDatabasePassword(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getDatabasePassword()', + 'applicationEnvironmentGitRef(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getGitRef()', + 'applicationEnvironmentDomain(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getDomain()', + 'applicationEnvironmentServerIps(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getServerIps()', + 'applicationEnvironmentWorkerServerIp(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getWorkerServerIp()', + 'applicationEnvironmentConfig(name,key)' => 'getApplicationEnvironmentByEnvironmentName(name).getConfig(key)', + 'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', ]; $this->assertEquals($expected, QuuxApplication::getTemplateReplacements()); diff --git a/Tests/Entity/ApplicationEnvironmentTest.php b/Tests/Entity/ApplicationEnvironmentTest.php index 7d023ab..7488c61 100644 --- a/Tests/Entity/ApplicationEnvironmentTest.php +++ b/Tests/Entity/ApplicationEnvironmentTest.php @@ -24,7 +24,14 @@ public function testTemplateReplacements() $expected = [ 'serverIps()' => 'getServerIps()', 'workerServerIp()' => 'getWorkerServerIp()', - 'environmentName()' => 'getEnvironment().getName()', + 'environmentName()' => 'getEnvironmentName()', + 'applicationName()' => 'getApplication().getName()', + 'applicationNameCanonical()' => 'getApplication().getNameCanonical()', + 'applicationGitRepo()' => 'getApplication().getGitRepo()', + 'applicationServerIps(dev_environment_name)' => 'getApplication().getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()', + 'environmentGitRef()' => 'getEnvironment().getGitRef()', + 'environmentConfig(key)' => 'getEnvironment().getConfig(key)', + 'environmentPriority()' => 'getEnvironment().getPriority()', 'config(key)' => 'getConfig(key)', 'databaseName()' => 'getDatabaseName()', 'databaseUser()' => 'getDatabaseUser()', diff --git a/Tests/Fixtures/Entity/Foo.php b/Tests/Fixtures/Entity/Foo.php index 83db261..1addd9e 100644 --- a/Tests/Fixtures/Entity/Foo.php +++ b/Tests/Fixtures/Entity/Foo.php @@ -5,11 +5,13 @@ use DigipolisGent\Domainator9k\CoreBundle\Entity\TemplateInterface; use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\IdentifiableTrait; +use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; class Foo implements TemplateInterface { use IdentifiableTrait; + use TemplateImplementationTrait; private $primaryTitle; @@ -17,13 +19,11 @@ class Foo implements TemplateInterface private $qux; - public static function getTemplateReplacements(): array + public static function additionalTemplateReplacements(): array { return [ 'primary()' => 'getPrimaryTitle()', 'second()' => 'getSecondTitle()', - 'quxTitle()' => 'getQux().getTitle()', - 'quxSubtitle()' => 'getQux().getSubtitle()', 'multiply(a,b)' => 'multiplyNumbers(a,b)', ]; } @@ -31,7 +31,7 @@ public static function getTemplateReplacements(): array /** * @return mixed */ - public function getPrimaryTitle() + public function getPrimaryTitle(): string { return $this->primaryTitle; } @@ -39,7 +39,7 @@ public function getPrimaryTitle() /** * @param mixed $primaryTitle */ - public function setPrimaryTitle($primaryTitle) + public function setPrimaryTitle(string $primaryTitle) { $this->primaryTitle = $primaryTitle; } @@ -47,7 +47,7 @@ public function setPrimaryTitle($primaryTitle) /** * @return mixed */ - public function getSecondTitle() + public function getSecondTitle(): string { return $this->secondTitle; } @@ -55,7 +55,7 @@ public function getSecondTitle() /** * @param mixed $secondTitle */ - public function setSecondTitle($secondTitle) + public function setSecondTitle(string $secondTitle) { $this->secondTitle = $secondTitle; } @@ -63,7 +63,7 @@ public function setSecondTitle($secondTitle) /** * @return mixed */ - public function getQux() + public function getQux(): Qux { return $this->qux; } @@ -76,7 +76,7 @@ public function setQux(Qux $qux) $this->qux = $qux; } - public function multiplyNumbers($a, $b) + public function multiplyNumbers(int $a, int $b): int { return $a * $b; } diff --git a/Tests/Fixtures/Entity/Qux.php b/Tests/Fixtures/Entity/Qux.php index ac93b3f..3a21150 100644 --- a/Tests/Fixtures/Entity/Qux.php +++ b/Tests/Fixtures/Entity/Qux.php @@ -5,28 +5,22 @@ use DigipolisGent\Domainator9k\CoreBundle\Entity\TemplateInterface; use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\IdentifiableTrait; +use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; class Qux implements TemplateInterface { use IdentifiableTrait; + use TemplateImplementationTrait; private $title; private $subtitle; - public static function getTemplateReplacements(): array - { - return [ - 'title()' => 'getTitle()', - 'subtitle()' => 'getSub()', - ]; - } - /** * @return mixed */ - public function getTitle() + public function getTitle(): string { return $this->title; } @@ -42,7 +36,7 @@ public function setTitle($title) /** * @return mixed */ - public function getSubtitle() + public function getSubtitle(): string { return $this->subtitle; } From d5894cbfe26fcab9caa3564a00b8114f64c5e42e Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:03:16 +0200 Subject: [PATCH 054/104] Added documentation. --- Entity/Traits/TemplateImplementationTrait.php | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/Entity/Traits/TemplateImplementationTrait.php b/Entity/Traits/TemplateImplementationTrait.php index bbbbe60..0409771 100644 --- a/Entity/Traits/TemplateImplementationTrait.php +++ b/Entity/Traits/TemplateImplementationTrait.php @@ -12,7 +12,7 @@ */ trait TemplateImplementationTrait { /** - * @return array + * {@inheritdoc} */ public static function getTemplateReplacements(int $maxDepth = 3, array $skip = []): array { @@ -20,7 +20,6 @@ public static function getTemplateReplacements(int $maxDepth = 3, array $skip = $skip[] = static::class; $methods = static::getRelevantMethods($reflection); $replacements = []; - $maxDepth--; foreach ($methods as $method) { $replacements += static::getTemplateReplacementsForMethod($method, $maxDepth, $skip); } @@ -32,8 +31,20 @@ public static function getTemplateReplacements(int $maxDepth = 3, array $skip = /** * Get relevant methods for template replacements. + * + * This function returns methods that are: + * - Not abstract or static + * - Start with -but are not equal to- 'get'. + * - Have a return type + * - Whose return type is scalar or implements TemplateInterface and is + * different from the current class (prevent loops). + * - Whose parameters are scalar (or non existant). + * * @param \ReflectionClass $class + * The class to get the relevant methods of. + * * @return \ReflectionMethod[] + * The relevant methods to build the templates. */ protected static function getRelevantMethods(ReflectionClass $class) { @@ -72,6 +83,23 @@ protected static function getRelevantMethods(ReflectionClass $class) return $relevantMethods; } + /** + * Get all template replacements for a method. + * + * If this method's return type is scalar it'll have one template. If the + * return type implements TemplateInterface, it is chained (as a prefix) to + * the templates of that return type. + * + * @param ReflectionMethod $method + * The method to get the templates for. + * @param int $maxDepth + * The maximum depth to chain. + * @param array $skip + * An array of classes to skip chaining for. + * + * @return array + * The templates generated for this method. + */ protected static function getTemplateReplacementsForMethod(ReflectionMethod $method, int $maxDepth, array $skip) { $returnType = $method->getReturnType(); @@ -81,24 +109,40 @@ protected static function getTemplateReplacementsForMethod(ReflectionMethod $met } $replacementParameters = implode(',', $parameters); $replacementCallback = $method->getName() . '(' . $replacementParameters . ')'; + + // Scalar return type, do not chain. if ($returnType->isBuiltin()) { + // Strip off 'get' from the keyword and lowercase the first letter. $template = lcfirst(substr($method->getName(), 3)) . '(' . $replacementParameters . ')'; return [$template => $replacementCallback]; } - if ($maxDepth < 0 || in_array((string)$returnType, $skip)) { + + // We've reached max depth or we should skip chaining for the return + // type. + if ($maxDepth <= 0 || in_array((string)$returnType, $skip)) { return []; } + + // Since method return type are usually more generic (interface, + // abstract class) we also check if the return type is a parent class of + // any of the classes to skip. foreach ($skip as $skipClass) { if (is_a($skipClass, (string) $returnType, true)) { return []; } } + + // Build the templates $replacements = []; + $maxDepth--; $subs = call_user_func(array((string)$returnType, 'getTemplateReplacements'), $maxDepth, $skip); foreach ($subs as $subTemplate => $replacementSubCallback) { + // Since we're chaining, we prepend the classname, with 'Abstract' or + // 'Interface' stripped off, to the method for uniqueness. $template = lcfirst( - str_replace('Abstract', '', ReflectionClass::createFromName((string)$returnType)->getShortName()) + str_replace(['Abstract', 'Interface'], ['', ''], ReflectionClass::createFromName((string)$returnType)->getShortName()) ) + // And we append the parameters of chained methods to the template. . ucfirst( str_replace( ['(,', ',)'], From 8a7c1d40c29fba00fc10b1c2954a6486d08d2e3b Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:06:58 +0200 Subject: [PATCH 055/104] Added roave/better-reflection dependency. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1fed448..07dabe3 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "phpseclib/phpseclib": "^2.0", "webmozart/path-util": "^2.3", "doctrine/doctrine-fixtures-bundle": "^2.3", - "mattketmo/camel": "^1.1" + "mattketmo/camel": "^1.1", + "roave/better-reflection": "^3" }, "require-dev": { "phpunit/phpunit": "6.5" From 9779e4a1bfb739a45dcb6072b63447826154321b Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:19:15 +0200 Subject: [PATCH 056/104] Refactoring based on codeclimate remarks. --- Entity/Traits/TemplateImplementationTrait.php | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Entity/Traits/TemplateImplementationTrait.php b/Entity/Traits/TemplateImplementationTrait.php index 0409771..d12cedf 100644 --- a/Entity/Traits/TemplateImplementationTrait.php +++ b/Entity/Traits/TemplateImplementationTrait.php @@ -86,7 +86,7 @@ protected static function getRelevantMethods(ReflectionClass $class) /** * Get all template replacements for a method. * - * If this method's return type is scalar it'll have one template. If the + * If this method's return type is scalar, it'll have one template. If the * return type implements TemplateInterface, it is chained (as a prefix) to * the templates of that return type. * @@ -131,7 +131,34 @@ protected static function getTemplateReplacementsForMethod(ReflectionMethod $met return []; } } + return static::getSubReplacementsForMethod($method, $maxDepth, $skip); + } + /** + * Get all subtemplate replacements for a method. + * + * The methods return type should implement TemplateInterface. This return + * type is chained (as a prefix) to the templates of that return type. + * + * @param ReflectionMethod $method + * The method to get the templates for. + * @param int $maxDepth + * The maximum depth to chain. + * @param array $skip + * An array of classes to skip chaining for. + * + * @return array + * The templates generated for this method. + */ + protected static function getSubReplacementsForMethod(ReflectionMethod $method, int $maxDepth, array $skip) + { + $returnType = $method->getReturnType(); + $parameters = []; + foreach ($method->getParameters() as $parameter) { + $parameters[] = $parameter->getName(); + } + $replacementParameters = implode(',', $parameters); + $replacementCallback = $method->getName() . '(' . $replacementParameters . ')'; // Build the templates $replacements = []; $maxDepth--; From ecfa122337ac622b2a89ea0d8414bb3c8a0da68d Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:23:31 +0200 Subject: [PATCH 057/104] Fix code style issues detected by codeclimate. --- Entity/Traits/TemplateImplementationTrait.php | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Entity/Traits/TemplateImplementationTrait.php b/Entity/Traits/TemplateImplementationTrait.php index d12cedf..0cc1125 100644 --- a/Entity/Traits/TemplateImplementationTrait.php +++ b/Entity/Traits/TemplateImplementationTrait.php @@ -10,7 +10,8 @@ * Trait IdentifiableTrait * @package DigipolisGent\Domainator9k\CoreBundle\Entity\Traits */ -trait TemplateImplementationTrait { +trait TemplateImplementationTrait +{ /** * {@inheritdoc} */ @@ -68,15 +69,21 @@ protected static function getRelevantMethods(ReflectionClass $class) $returnType = $method->getReturnType(); // Whose return type is scalar or implements TemplateInterface and // is different from the current class (prevent loops). - if (!$returnType->isBuiltin() && (!class_exists($returnType) || !is_a((string)$returnType, TemplateInterface::class, true))) { + if ( + !$returnType->isBuiltin() + && ( + !class_exists($returnType) + || !is_a((string)$returnType, TemplateInterface::class, true) + ) + ) { continue; } // Whose parameters are scalar (or non existant). foreach ($method->getParameters() as $parameter) { - $parameterType = $parameter->getType(); - if (!is_null($parameterType) && !$parameterType->isBuiltin()) { - continue 2; - } + $parameterType = $parameter->getType(); + if (!is_null($parameterType) && !$parameterType->isBuiltin()) { + continue 2; + } } $relevantMethods[$method->getName()] = $method; } @@ -105,7 +112,7 @@ protected static function getTemplateReplacementsForMethod(ReflectionMethod $met $returnType = $method->getReturnType(); $parameters = []; foreach ($method->getParameters() as $parameter) { - $parameters[] = $parameter->getName(); + $parameters[] = $parameter->getName(); } $replacementParameters = implode(',', $parameters); $replacementCallback = $method->getName() . '(' . $replacementParameters . ')'; @@ -127,9 +134,9 @@ protected static function getTemplateReplacementsForMethod(ReflectionMethod $met // abstract class) we also check if the return type is a parent class of // any of the classes to skip. foreach ($skip as $skipClass) { - if (is_a($skipClass, (string) $returnType, true)) { - return []; - } + if (is_a($skipClass, (string) $returnType, true)) { + return []; + } } return static::getSubReplacementsForMethod($method, $maxDepth, $skip); } @@ -155,7 +162,7 @@ protected static function getSubReplacementsForMethod(ReflectionMethod $method, $returnType = $method->getReturnType(); $parameters = []; foreach ($method->getParameters() as $parameter) { - $parameters[] = $parameter->getName(); + $parameters[] = $parameter->getName(); } $replacementParameters = implode(',', $parameters); $replacementCallback = $method->getName() . '(' . $replacementParameters . ')'; @@ -167,11 +174,15 @@ protected static function getSubReplacementsForMethod(ReflectionMethod $method, // Since we're chaining, we prepend the classname, with 'Abstract' or // 'Interface' stripped off, to the method for uniqueness. $template = lcfirst( - str_replace(['Abstract', 'Interface'], ['', ''], ReflectionClass::createFromName((string)$returnType)->getShortName()) + str_replace( + ['Abstract', 'Interface'], + ['', ''], + ReflectionClass::createFromName((string)$returnType)->getShortName() + ) ) // And we append the parameters of chained methods to the template. . ucfirst( - str_replace( + str_replace( ['(,', ',)'], ['(', ')'], preg_replace( @@ -179,7 +190,7 @@ protected static function getSubReplacementsForMethod(ReflectionMethod $method, '(' . $replacementParameters . ',$1)', $subTemplate ) - ) + ) ); $replacements[$template] = $replacementCallback . '.' . $replacementSubCallback; } From 7ca829654e51eb7a832100085471182e40b38b2d Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:29:58 +0200 Subject: [PATCH 058/104] Fix return types for AbstractApplication. --- Entity/AbstractApplication.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index e82959a..c44fc2b 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -122,7 +122,7 @@ public function getNameCanonical(): ?string /** * @return string */ - public function getGitRepo(): string + public function getGitRepo(): ?string { return $this->gitRepo; } @@ -138,7 +138,7 @@ public function setGitRepo(string $gitRepo) /** * @return bool */ - public function isHasDatabase(): bool + public function isHasDatabase(): ?bool { return $this->hasDatabase; } @@ -205,7 +205,7 @@ public static function additionalTemplateReplacements(): array /** * @return bool */ - public function isDeleted(): bool + public function isDeleted(): ?bool { return $this->deleted; } From 58d36345a21143598623169a6f92671b2dc6e1a7 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:39:34 +0200 Subject: [PATCH 059/104] Code style fixes. --- Twig/TemplateHelpExtension.php | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Twig/TemplateHelpExtension.php b/Twig/TemplateHelpExtension.php index b2359a4..02c61f3 100644 --- a/Twig/TemplateHelpExtension.php +++ b/Twig/TemplateHelpExtension.php @@ -19,20 +19,28 @@ public function __construct(TokenService $tokenService) { public function getFunctions() { - return array( + return [ new TwigFunction( 'template_help', - array($this, 'templateHelp'), - array('needs_environment' => true, 'is_safe' => array('html')) + [ + $this, + 'templateHelp', + ], + [ + 'needs_environment' => true, + 'is_safe' => [ + 'html', + ], + ] ), - ); + ]; } public function templateHelp(Environment $environment, array $classes, $textarea) { $templates = [ - 'token' => array_keys($this->tokenService->getTemplateReplacements()), + 'token' => array_keys($this->tokenService->getTemplateReplacements()), ]; foreach ($classes as $key => $class) { if (!is_a($class, TemplateInterface::class, true)) { @@ -41,6 +49,11 @@ public function templateHelp(Environment $environment, array $classes, $textarea $templates[$key] = array_keys(call_user_func([$class, 'getTemplateReplacements'])); } - return $environment->render('@DigipolisGentDomainator9kCore/Template/templatehelper.twig', ['templates' => $templates, 'textarea' => $textarea]); + return $environment->render( + '@DigipolisGentDomainator9kCore/Template/templatehelper.twig', + [ + 'templates' => $templates, + 'textarea' => $textarea, + ]); } } From d96afd2fd06b63b4c7e6227a2de6f4e81aa1b26d Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:51:11 +0200 Subject: [PATCH 060/104] Fix indentation. --- Twig/TemplateHelpExtension.php | 95 +++++++++++++++++----------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/Twig/TemplateHelpExtension.php b/Twig/TemplateHelpExtension.php index 02c61f3..cbb1d16 100644 --- a/Twig/TemplateHelpExtension.php +++ b/Twig/TemplateHelpExtension.php @@ -9,51 +9,52 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; -class TemplateHelpExtension extends AbstractExtension { - - protected $tokenService; - - public function __construct(TokenService $tokenService) { - $this->tokenService = $tokenService; - } - - public function getFunctions() - { - return [ - new TwigFunction( - 'template_help', - [ - $this, - 'templateHelp', - ], - [ - 'needs_environment' => true, - 'is_safe' => [ - 'html', - ], - ] - ), - ]; - } - - public function templateHelp(Environment $environment, array $classes, $textarea) - { - - $templates = [ - 'token' => array_keys($this->tokenService->getTemplateReplacements()), - ]; - foreach ($classes as $key => $class) { - if (!is_a($class, TemplateInterface::class, true)) { - new RuntimeError(sprintf('Class %s does not implement %s.', $class, TemplateInterface::class)); - } - $templates[$key] = array_keys(call_user_func([$class, 'getTemplateReplacements'])); - } - - return $environment->render( - '@DigipolisGentDomainator9kCore/Template/templatehelper.twig', - [ - 'templates' => $templates, - 'textarea' => $textarea, - ]); - } +class TemplateHelpExtension extends AbstractExtension +{ + + protected $tokenService; + + public function __construct(TokenService $tokenService) { + $this->tokenService = $tokenService; + } + + public function getFunctions() + { + return [ + new TwigFunction( + 'template_help', + [ + $this, + 'templateHelp', + ], + [ + 'needs_environment' => true, + 'is_safe' => [ + 'html', + ], + ] + ), + ]; + } + + public function templateHelp(Environment $environment, array $classes, $textarea) + { + + $templates = [ + 'token' => array_keys($this->tokenService->getTemplateReplacements()), + ]; + foreach ($classes as $key => $class) { + if (!is_a($class, TemplateInterface::class, true)) { + new RuntimeError(sprintf('Class %s does not implement %s.', $class, TemplateInterface::class)); + } + $templates[$key] = array_keys(call_user_func([$class, 'getTemplateReplacements'])); + } + + return $environment->render( + '@DigipolisGentDomainator9kCore/Template/templatehelper.twig', + [ + 'templates' => $templates, + 'textarea' => $textarea, + ]); + } } From 55233de8f3009ca2b0890d7e4924d85cc38a4b58 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 4 Jun 2018 19:52:50 +0200 Subject: [PATCH 061/104] Code style fixes. --- Twig/TemplateHelpExtension.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Twig/TemplateHelpExtension.php b/Twig/TemplateHelpExtension.php index cbb1d16..00fb1b5 100644 --- a/Twig/TemplateHelpExtension.php +++ b/Twig/TemplateHelpExtension.php @@ -14,7 +14,8 @@ class TemplateHelpExtension extends AbstractExtension protected $tokenService; - public function __construct(TokenService $tokenService) { + public function __construct(TokenService $tokenService) + { $this->tokenService = $tokenService; } @@ -55,6 +56,7 @@ public function templateHelp(Environment $environment, array $classes, $textarea [ 'templates' => $templates, 'textarea' => $textarea, - ]); + ] + ); } } From 52a2839d2f9036e2a7b89586e94b6ee63afb448a Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 5 Jun 2018 09:14:34 +0200 Subject: [PATCH 062/104] Refactor for readability. --- Entity/Traits/TemplateImplementationTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Entity/Traits/TemplateImplementationTrait.php b/Entity/Traits/TemplateImplementationTrait.php index 0cc1125..f0301be 100644 --- a/Entity/Traits/TemplateImplementationTrait.php +++ b/Entity/Traits/TemplateImplementationTrait.php @@ -179,9 +179,9 @@ protected static function getSubReplacementsForMethod(ReflectionMethod $method, ['', ''], ReflectionClass::createFromName((string)$returnType)->getShortName() ) - ) + ); // And we append the parameters of chained methods to the template. - . ucfirst( + $template .= ucfirst( str_replace( ['(,', ',)'], ['(', ')'], From 0926b9deecec2436c5c686ec7c42b6eea9683fe8 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Wed, 6 Jun 2018 10:24:25 +0200 Subject: [PATCH 063/104] Added support for dialog polyfill. --- Resources/public/js/templatehelper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Resources/public/js/templatehelper.js b/Resources/public/js/templatehelper.js index 4c918a7..ad20793 100644 --- a/Resources/public/js/templatehelper.js +++ b/Resources/public/js/templatehelper.js @@ -3,6 +3,9 @@ document.addEventListener("DOMContentLoaded", function () { document.querySelectorAll('dialog[data-template-helper-textarea]').forEach(function (dialog) { if (!dialog.dataset.templateHelperProcessed) { + if (typeof window.dialogPolyfill !== 'undefined') { + window.dialogPolyfill.registerDialog(dialog); + } var selector = dialog.dataset.templateHelperTextarea; window.templateHelpers[selector] = new TemplateHelper(dialog); dialog.dataset.templateHelperProcessed = true; From f5cedbfe168ae60b68d5de8d76707f10f17f663d Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Wed, 6 Jun 2018 13:19:39 +0200 Subject: [PATCH 064/104] Fix return types for ArrayCollection to Collection. --- Entity/AbstractApplication.php | 3 ++- Entity/ApplicationEnvironment.php | 3 ++- Entity/Environment.php | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index bcc5e5c..d262872 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -7,6 +7,7 @@ use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; use DigipolisGent\SettingBundle\Entity\Traits\SettingImplementationTrait; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Validator\Constraints as Assert; @@ -171,7 +172,7 @@ public function removeApplicationEnvironment(ApplicationEnvironment $application /** * @return ArrayCollection */ - public function getApplicationEnvironments(): ArrayCollection + public function getApplicationEnvironments(): Collection { return $this->applicationEnvironments; } diff --git a/Entity/ApplicationEnvironment.php b/Entity/ApplicationEnvironment.php index f443151..df91eb4 100644 --- a/Entity/ApplicationEnvironment.php +++ b/Entity/ApplicationEnvironment.php @@ -6,6 +6,7 @@ use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; use DigipolisGent\SettingBundle\Entity\Traits\SettingImplementationTrait; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; @@ -199,7 +200,7 @@ public function setGitRef(string $gitRef = null) /** * @return ArrayCollection */ - public function getTasks(): ArrayCollection + public function getTasks(): Collection { return $this->tasks; } diff --git a/Entity/Environment.php b/Entity/Environment.php index 036a24a..daa7e55 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -6,6 +6,7 @@ use DigipolisGent\Domainator9k\CoreBundle\Entity\Traits\TemplateImplementationTrait; use DigipolisGent\SettingBundle\Entity\Traits\SettingImplementationTrait; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Validator\Constraints as Assert; @@ -158,7 +159,7 @@ public function addApplicationEnvironment(ApplicationEnvironment $applicationEnv /** * @return ArrayCollection */ - public function getApplicationEnvironments(): ArrayCollection + public function getApplicationEnvironments(): Collection { return $this->applicationEnvironments; } @@ -174,7 +175,7 @@ public function addApplicationTypeEnvironment(ApplicationTypeEnvironment $applic /** * @return ArrayCollection */ - public function getApplicationTypeEnvironments(): ArrayCollection + public function getApplicationTypeEnvironments(): Collection { return $this->applicationTypeEnvironments; } @@ -191,7 +192,7 @@ public function addVirtualServer(VirtualServer $virtualServer) /** * @return ArrayCollection */ - public function getVirtualServers(): ArrayCollection + public function getVirtualServers(): Collection { return $this->virtualServers; } From e9044b135d7588f911ef51e74f82b29d59ef94ab Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Thu, 7 Jun 2018 15:53:13 +0200 Subject: [PATCH 065/104] WEBDOM-391: Convert build/destroy event listeners to tagged services. --- Entity/Task.php | 82 ++++++++++++++++++++++++ Provisioner/ProvisionerInterface.php | 10 +++ Resources/config/services.yml | 2 + Service/ProvisionService.php | 94 ++++++++++++++++++++++++++++ Service/TaskService.php | 59 +++-------------- 5 files changed, 198 insertions(+), 49 deletions(-) create mode 100644 Provisioner/ProvisionerInterface.php create mode 100644 Service/ProvisionService.php diff --git a/Entity/Task.php b/Entity/Task.php index c20a5ed..e8bb4fb 100644 --- a/Entity/Task.php +++ b/Entity/Task.php @@ -146,4 +146,86 @@ public function setLog($log) return $this; } + + /** + * Mark this task as in progress. + */ + public function setInProgress() + { + $this->setStatus(static::STATUS_IN_PROGRESS); + } + + /** + * Mark this task as processed. + */ + public function setProcessed() + { + $this->setStatus(static::STATUS_PROCESSED); + } + + /** + * Mark this task as failed. + */ + public function setFailed() + { + $this->setStatus(static::STATUS_FAILED); + } + + /** + * Mark this task as in cancelled. + */ + public function setCancelled() + { + $this->setStatus(static::STATUS_CANCEL); + } + + /** + * Check if this task is new. + * + * @return boolean + */ + public function isNew() + { + return $this->getStatus === static::STATUS_NEW; + } + + /** + * Check if this task is in progress. + * + * @return boolean + */ + public function isInProgress() + { + $this->setStatus(static::STATUS_IN_PROGRESS); + } + + /** + * Check if this task is processed. + * + * @return boolean + */ + public function isProcessed() + { + $this->setStatus(static::STATUS_PROCESSED); + } + + /** + * Check if this task is failed. + * + * @return boolean + */ + public function isFailed() + { + $this->setStatus(static::STATUS_FAILED); + } + + /** + * Check if this task is cancelled. + * + * @return boolean + */ + public function isCancelled() + { + $this->setStatus(static::STATUS_CANCEL); + } } diff --git a/Provisioner/ProvisionerInterface.php b/Provisioner/ProvisionerInterface.php new file mode 100644 index 0000000..ee84ad2 --- /dev/null +++ b/Provisioner/ProvisionerInterface.php @@ -0,0 +1,10 @@ +buildProvisioners = $buildProvisioners; + $this->destroyProvisioners = $destroyProvisioners; + } + + /** + * Run all provisioners for a task. + * + * @param Task $task + * The task to run the provisioners for. + * + * @throws \InvalidArgumentException + * If the task type is not supported. + */ + public function run(Task $task) + { + switch ($task->getType()) { + case Task::TYPE_BUILD: + $this->build($task); + break; + + case Task::TYPE_DESTROY: + $this->destroy($task); + break; + + default: + throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); + } + } + + /** + * Run a build task. + * + * @param Task $task + * The build task. + */ + protected function build(Task $task) + { + foreach ($this->buildProvisioners as $provisioner) { + $provisioner->run($task); + if ($task->isFailed()) { + break; + } + } + } + + /** + * Run a destroy task. + * + * @param Task $task + * The destroy task. + */ + protected function destroy(Task $task) { + foreach ($this->destroyProvisioners as $provisioner) { + $provisioner->run($task); + if ($task->isFailed()) { + break; + } + } + } +} diff --git a/Service/TaskService.php b/Service/TaskService.php index abaa644..25443b4 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -2,13 +2,9 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Service; -use DigipolisGent\Domainator9k\CoreBundle\Entity\Build; use DigipolisGent\Domainator9k\CoreBundle\Entity\Task; -use DigipolisGent\Domainator9k\CoreBundle\Event\AbstractEvent; -use DigipolisGent\Domainator9k\CoreBundle\Event\BuildEvent; -use DigipolisGent\Domainator9k\CoreBundle\Event\DestroyEvent; +use DigipolisGent\Domainator9k\CoreBundle\Service\ProvisionService; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Class TaskService @@ -32,24 +28,24 @@ class TaskService private $entityManager; /** - * The event dispatcher service. + * The provision service. * - * @var EventDispatcherInterface + * @var ProvisionService */ - private $eventDispatcher; + private $provisionService; /** * Class constructor. * * @param EntityManagerInterface $entityManager * The entity manager service. - * @param EventDispatcherInterface $eventDispatcher - * The event dispatcher service. + * @param ProvisionService $provisionService + * The provision service. */ - public function __construct(EntityManagerInterface $entityManager, EventDispatcherInterface $eventDispatcher) + public function __construct(EntityManagerInterface $entityManager, ProvisionService $provisionService) { $this->entityManager = $entityManager; - $this->eventDispatcher = $eventDispatcher; + $this->provisionService = $provisionService; } /** @@ -69,19 +65,11 @@ public function run(Task $task) $this->entityManager->persist($task); $this->entityManager->flush(); - // Create and dispatch the event. - $event = $this->createEvent($task); - $this->eventDispatcher->dispatch($event::NAME, $event); - $task = $event->getTask(); + $this->provisionService->run($task); // Update the status. if ($task->getStatus() === Task::STATUS_IN_PROGRESS) { - $status = Task::STATUS_PROCESSED; - if ($event->isPropagationStopped()) { - $status = Task::STATUS_FAILED; - } - - $task->setStatus($status); + $task->setStatus(Task::STATUS_PROCESSED); } // Add a log message or simply persist any changes. @@ -308,33 +296,6 @@ public function addFailedLogMessage(Task $task, string $message, int $indent = 1 return $this->addLogMessage($task, self::LOG_TYPE_FAILED, $message, $indent, $persist); } - /** - * Create an event object for a task. - * - * @param Task $task - * The task. - * - * @return AbstractEvent - * The event object. - */ - protected function createEvent(Task $task): AbstractEvent - { - switch ($task->getType()) { - case Task::TYPE_BUILD: - $class = BuildEvent::class; - break; - - case Task::TYPE_DESTROY: - $class = DestroyEvent::class; - break; - - default: - throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); - } - - return new $class($task); - } - /** * Indent a text. * From 664f4301959d5e02fc85c7aba38c104518c6d9ff Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Thu, 7 Jun 2018 16:50:17 +0200 Subject: [PATCH 066/104] WEBDOM-391: Add and fix tests. --- Entity/Task.php | 10 +- Service/ProvisionService.php | 3 +- Tests/Service/ProvisionServiceTest.php | 172 +++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 Tests/Service/ProvisionServiceTest.php diff --git a/Entity/Task.php b/Entity/Task.php index e8bb4fb..0838f72 100644 --- a/Entity/Task.php +++ b/Entity/Task.php @@ -186,7 +186,7 @@ public function setCancelled() */ public function isNew() { - return $this->getStatus === static::STATUS_NEW; + return $this->getStatus() === static::STATUS_NEW; } /** @@ -196,7 +196,7 @@ public function isNew() */ public function isInProgress() { - $this->setStatus(static::STATUS_IN_PROGRESS); + return $this->getStatus() === static::STATUS_IN_PROGRESS; } /** @@ -206,7 +206,7 @@ public function isInProgress() */ public function isProcessed() { - $this->setStatus(static::STATUS_PROCESSED); + return $this->getStatus() === static::STATUS_PROCESSED; } /** @@ -216,7 +216,7 @@ public function isProcessed() */ public function isFailed() { - $this->setStatus(static::STATUS_FAILED); + return $this->getStatus() === static::STATUS_FAILED; } /** @@ -226,6 +226,6 @@ public function isFailed() */ public function isCancelled() { - $this->setStatus(static::STATUS_CANCEL); + return $this->getStatus() === static::STATUS_CANCEL; } } diff --git a/Service/ProvisionService.php b/Service/ProvisionService.php index e2bf289..80b8fea 100644 --- a/Service/ProvisionService.php +++ b/Service/ProvisionService.php @@ -83,7 +83,8 @@ protected function build(Task $task) * @param Task $task * The destroy task. */ - protected function destroy(Task $task) { + protected function destroy(Task $task) + { foreach ($this->destroyProvisioners as $provisioner) { $provisioner->run($task); if ($task->isFailed()) { diff --git a/Tests/Service/ProvisionServiceTest.php b/Tests/Service/ProvisionServiceTest.php new file mode 100644 index 0000000..72987df --- /dev/null +++ b/Tests/Service/ProvisionServiceTest.php @@ -0,0 +1,172 @@ +setName(substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10)); + $token->setValue(substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10)); + $this->token = $token; + $this->repository = $this + ->getMockBuilder(EntityRepository::class) + ->disableOriginalConstructor() + ->getMock(); + $this->repository->expects($this->any())->method('findAll')->willReturn([$token]); + $this->repository->expects($this->any())->method('findOneBy')->with(['name' => $token->getName()])->willReturn($token); + $this->entityManager = $this + ->getMockBuilder(EntityManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->entityManager + ->expects($this->once()) + ->method('getRepository') + ->with(Token::class) + ->willReturn($this->repository); + $this->tokenService = new TokenService($this->entityManager); + } + + public function testBuild() + { + $task = new Task(); + $task->setType(Task::TYPE_BUILD); + + $buildProvisioners = []; + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($task) + ->willReturn(null); + $buildProvisioners[] = $mock; + } + $destroyProvisioners = []; + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->never()) + ->method('run'); + $destroyProvisioners[] = $mock; + } + $provisionService = new ProvisionService($buildProvisioners, $destroyProvisioners); + $provisionService->run($task); + } + + public function testDestroy() + { + $task = new Task(); + $task->setType(Task::TYPE_DESTROY); + + $buildProvisioners = []; + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->never()) + ->method('run'); + $buildProvisioners[] = $mock; + } + + $destroyProvisioners = []; + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($task) + ->willReturn(null); + $destroyProvisioners[] = $mock; + } + $provisionService = new ProvisionService($buildProvisioners, $destroyProvisioners); + $provisionService->run($task); + } + + public function testFailedBuild() + { + $task = new Task(); + $task->setType(Task::TYPE_BUILD); + + $buildProvisioners = []; + foreach (range(0, 3) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($task) + ->willReturn(null); + $buildProvisioners[] = $mock; + } + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($task) + ->willReturnCallback(function (Task $task) { + $task->setFailed(); + }); + $buildProvisioners[] = $mock; + + foreach (range(0, 2) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->never()) + ->method('run'); + $buildProvisioners[] = $mock; + } + $provisionService = new ProvisionService($buildProvisioners, []); + $provisionService->run($task); + } + + public function testFailedDestroy() + { + $task = new Task(); + $task->setType(Task::TYPE_DESTROY); + + $destroyProvisioners = []; + foreach (range(0, 3) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($task) + ->willReturn(null); + $destroyProvisioners[] = $mock; + } + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($task) + ->willReturnCallback(function (Task $task) { + $task->setFailed(); + }); + $destroyProvisioners[] = $mock; + + foreach (range(0, 2) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->never()) + ->method('run'); + $destroyProvisioners[] = $mock; + } + $provisionService = new ProvisionService([], $destroyProvisioners); + $provisionService->run($task); + } +} From 8fc0d364f114658929c559ad3850e0cb36986d24 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 09:37:04 +0200 Subject: [PATCH 067/104] WEBDOM-91: Use newly provided Task methods to check status. --- Service/ProvisionService.php | 6 ++++++ Service/TaskService.php | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Service/ProvisionService.php b/Service/ProvisionService.php index 80b8fea..7e8c900 100644 --- a/Service/ProvisionService.php +++ b/Service/ProvisionService.php @@ -42,6 +42,10 @@ public function __construct(iterable $buildProvisioners, iterable $destroyProvis * @param Task $task * The task to run the provisioners for. * + * @return boolean + * True when the task has been processed succesfully, false for any other + * status. + * * @throws \InvalidArgumentException * If the task type is not supported. */ @@ -59,6 +63,8 @@ public function run(Task $task) default: throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); } + + return $task->isProcessed(); } /** diff --git a/Service/TaskService.php b/Service/TaskService.php index 25443b4..1d51b98 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -56,30 +56,30 @@ public function __construct(EntityManagerInterface $entityManager, ProvisionServ */ public function run(Task $task) { - if ($task->getStatus() !== Task::STATUS_NEW) { + if (!$task->isNew()) { throw new \InvalidArgumentException(sprintf('Task "%s" cannot be restarted.', $task->getId())); } // Set the task in progress. - $task->setStatus(Task::STATUS_IN_PROGRESS); + $task->setInProgress(); $this->entityManager->persist($task); $this->entityManager->flush(); $this->provisionService->run($task); // Update the status. - if ($task->getStatus() === Task::STATUS_IN_PROGRESS) { - $task->setStatus(Task::STATUS_PROCESSED); + if ($task->isInProgress()) { + $task->setProcessed(); } // Add a log message or simply persist any changes. - switch ($task->getStatus()) { - case Task::STATUS_PROCESSED: + switch (true) { + case $task->isProcessed(): $this->addLogMessage($task, '', '', 0); $this->addSuccessLogMessage($task, 'Task run completed.', 0); break; - case Task::STATUS_FAILED: + case $task->isFailed(): $this->addLogMessage($task, '', '', 0); $this->addFailedLogMessage($task, 'Task run failed.', 0); break; From 99c6f6406f89920abba0d73d9679c7385aba4165 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 09:47:26 +0200 Subject: [PATCH 068/104] WEBDOM-391: Add test for invalid task type. --- Tests/Service/ProvisionServiceTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/Service/ProvisionServiceTest.php b/Tests/Service/ProvisionServiceTest.php index 72987df..c2658e9 100644 --- a/Tests/Service/ProvisionServiceTest.php +++ b/Tests/Service/ProvisionServiceTest.php @@ -169,4 +169,16 @@ public function testFailedDestroy() $provisionService = new ProvisionService([], $destroyProvisioners); $provisionService->run($task); } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Task type (.*) is not supported\./ + */ + public function testInvalidType () + { + $task = new Task(); + $task->setType(uniqid()); + $provisionService = new ProvisionService([], []); + $provisionService->run($task); + } } From 81a9d2cbbb4ca2fbb4906b6f439bf03e2765d249 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 10:41:27 +0200 Subject: [PATCH 069/104] WEBDOM-391: Added (some) test coverage for TaskService. --- Service/ProvisionService.php | 6 - Service/TaskService.php | 6 + Tests/Service/ProvisionServiceTest.php | 27 +-- Tests/Service/TaskServiceTest.php | 282 +++++++++++++++++++++++++ 4 files changed, 289 insertions(+), 32 deletions(-) create mode 100644 Tests/Service/TaskServiceTest.php diff --git a/Service/ProvisionService.php b/Service/ProvisionService.php index 7e8c900..80b8fea 100644 --- a/Service/ProvisionService.php +++ b/Service/ProvisionService.php @@ -42,10 +42,6 @@ public function __construct(iterable $buildProvisioners, iterable $destroyProvis * @param Task $task * The task to run the provisioners for. * - * @return boolean - * True when the task has been processed succesfully, false for any other - * status. - * * @throws \InvalidArgumentException * If the task type is not supported. */ @@ -63,8 +59,6 @@ public function run(Task $task) default: throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); } - - return $task->isProcessed(); } /** diff --git a/Service/TaskService.php b/Service/TaskService.php index 1d51b98..85c8e4b 100644 --- a/Service/TaskService.php +++ b/Service/TaskService.php @@ -53,6 +53,10 @@ public function __construct(EntityManagerInterface $entityManager, ProvisionServ * * @param Task $task * The task to run. + * + * @return boolean + * True when the task has been processed succesfully, false for any other + * status. */ public function run(Task $task) { @@ -89,6 +93,8 @@ public function run(Task $task) $this->entityManager->flush(); break; } + + return $task->isProcessed(); } /** diff --git a/Tests/Service/ProvisionServiceTest.php b/Tests/Service/ProvisionServiceTest.php index c2658e9..ab6bd60 100644 --- a/Tests/Service/ProvisionServiceTest.php +++ b/Tests/Service/ProvisionServiceTest.php @@ -18,31 +18,6 @@ class ProvisionServiceTest extends TestCase { - protected function setUp() - { - parent::setUp(); - $token = new Token(); - $token->setName(substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10)); - $token->setValue(substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10)); - $this->token = $token; - $this->repository = $this - ->getMockBuilder(EntityRepository::class) - ->disableOriginalConstructor() - ->getMock(); - $this->repository->expects($this->any())->method('findAll')->willReturn([$token]); - $this->repository->expects($this->any())->method('findOneBy')->with(['name' => $token->getName()])->willReturn($token); - $this->entityManager = $this - ->getMockBuilder(EntityManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->entityManager - ->expects($this->once()) - ->method('getRepository') - ->with(Token::class) - ->willReturn($this->repository); - $this->tokenService = new TokenService($this->entityManager); - } - public function testBuild() { $task = new Task(); @@ -174,7 +149,7 @@ public function testFailedDestroy() * @expectedException \InvalidArgumentException * @expectedExceptionMessageRegExp /Task type (.*) is not supported\./ */ - public function testInvalidType () + public function testInvalidType() { $task = new Task(); $task->setType(uniqid()); diff --git a/Tests/Service/TaskServiceTest.php b/Tests/Service/TaskServiceTest.php new file mode 100644 index 0000000..3dc4053 --- /dev/null +++ b/Tests/Service/TaskServiceTest.php @@ -0,0 +1,282 @@ +task = new Task(); + $this->task->setType(Task::TYPE_BUILD); + $id = uniqid(); + $prop = new \ReflectionProperty($this->task, 'id'); + $prop->setAccessible(true); + $prop->setValue($this->task, $id); + + $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class) + ->getMock(); + + $this->provisionService = $this->getMockBuilder(ProvisionService::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->entityManagerIndex = 0; + $this->provisionServiceIndex = 0; + + $this->taskService = new TaskService($this->entityManager, $this->provisionService); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Task "(.*)" cannot be restarted\./ + */ + public function testRunNotNew() + { + $this->task->setProcessed(); + $this->taskService->run($this->task); + } + + public function testRunSuccess() + { + $this->expectSuccessfulRun(); + + $this->taskService->run($this->task); + } + + public function testRunFailed() + { + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isInProgress(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->provisionService + ->expects($this->at($this->provisionServiceIndex++)) + ->method('run') + ->with($this->callback( + function (Task $task) { + return $task->isInProgress(); + } + )) + ->willReturnCallback(function (Task $task) { + $task->setFailed(); + }); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) { + return $task->isFailed(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) { + return $task->isFailed() && (strpos($task->getLog(), 'Task run failed.') !== false); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->taskService->run($this->task); + } + + public function testRunCancelled() + { + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isInProgress(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->provisionService + ->expects($this->at($this->provisionServiceIndex++)) + ->method('run') + ->with($this->callback( + function (Task $task) { + return $task->isInProgress(); + } + )) + ->willReturnCallback(function (Task $task) { + $task->setCancelled(); + }); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) { + return $task->isCancelled() && is_null($task->getLog()); + } + )); + + $this->taskService->run($this->task); + } + + public function testRunNext() + { + $repository = $this->getMockBuilder(TaskRepository::class) + ->disableOriginalConstructor() + ->getMock(); + $repository->expects($this->at(0)) + ->method('getNextTask') + ->with(Task::TYPE_BUILD) + ->willReturn($this->task); + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('getRepository') + ->with(Task::class) + ->willReturn($repository); + $this->expectSuccessfulRun(); + $this->taskService->runNext(Task::TYPE_BUILD); + } + + public function testCancel() + { + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isCancelled() && (strpos($task->getLog(), 'Task run cancelled.') !== false); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->taskService->cancel($this->task); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Task (.*) cannot be cancelled\./ + */ + public function testCancelRunning() + { + $this->task->setInProgress(); + $this->taskService->cancel($this->task); + } + + protected function expectSuccessfulRun() + { + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isInProgress(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->provisionService + ->expects($this->at($this->provisionServiceIndex++)) + ->method('run') + ->with($this->callback( + function (Task $task) { + return $task->isInProgress(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) { + return $task->isProcessed(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) { + return $task->isProcessed() && (strpos($task->getLog(), 'Task run completed.') !== FALSE); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + } +} From 00ee7ec15e7192954c3c6d14e011550bf09fba1b Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 16:30:16 +0200 Subject: [PATCH 070/104] WEBDOM-391: [WIP] Refactor Task related services. --- Resources/config/services.yml | 10 +- Service/ProvisionService.php | 95 ----- ...{TaskService.php => TaskLoggerService.php} | 102 +---- Service/TaskRunnerService.php | 212 ++++++++++ Tests/Service/ProvisionServiceTest.php | 159 -------- Tests/Service/TaskRunnerServiceTest.php | 370 ++++++++++++++++++ Tests/Service/TaskServiceTest.php | 282 ------------- 7 files changed, 593 insertions(+), 637 deletions(-) delete mode 100644 Service/ProvisionService.php rename Service/{TaskService.php => TaskLoggerService.php} (70%) create mode 100644 Service/TaskRunnerService.php delete mode 100644 Tests/Service/ProvisionServiceTest.php create mode 100644 Tests/Service/TaskRunnerServiceTest.php delete mode 100644 Tests/Service/TaskServiceTest.php diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 0cad915..69a1b09 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -5,11 +5,15 @@ services: tags: - { name: doctrine.event_listener, event: postPersist, connection: default } - { name: doctrine.event_listener, event: postRemove, connection: default } - DigipolisGent\Domainator9k\CoreBundle\Service\TaskService: + DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService: DigipolisGent\Domainator9k\CoreBundle\Service\TemplateService: DigipolisGent\Domainator9k\CoreBundle\Service\TokenService: - DigipolisGent\Domainator9k\CoreBundle\Service\ProvisionService: - arguments: [!tagged domainator.provisioner.build, !tagged domainator.provisioner.destroy] + DigipolisGent\Domainator9k\CoreBundle\Service\TaskRunnerService: + arguments: [ + !tagged domainator.provisioner.build, + !tagged domainator.provisioner.destroy, + '@doctrine.orm.entity_manager' + ] DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationEnvironmentFormType: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\EnvironmentFormType: diff --git a/Service/ProvisionService.php b/Service/ProvisionService.php deleted file mode 100644 index 80b8fea..0000000 --- a/Service/ProvisionService.php +++ /dev/null @@ -1,95 +0,0 @@ -buildProvisioners = $buildProvisioners; - $this->destroyProvisioners = $destroyProvisioners; - } - - /** - * Run all provisioners for a task. - * - * @param Task $task - * The task to run the provisioners for. - * - * @throws \InvalidArgumentException - * If the task type is not supported. - */ - public function run(Task $task) - { - switch ($task->getType()) { - case Task::TYPE_BUILD: - $this->build($task); - break; - - case Task::TYPE_DESTROY: - $this->destroy($task); - break; - - default: - throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); - } - } - - /** - * Run a build task. - * - * @param Task $task - * The build task. - */ - protected function build(Task $task) - { - foreach ($this->buildProvisioners as $provisioner) { - $provisioner->run($task); - if ($task->isFailed()) { - break; - } - } - } - - /** - * Run a destroy task. - * - * @param Task $task - * The destroy task. - */ - protected function destroy(Task $task) - { - foreach ($this->destroyProvisioners as $provisioner) { - $provisioner->run($task); - if ($task->isFailed()) { - break; - } - } - } -} diff --git a/Service/TaskService.php b/Service/TaskLoggerService.php similarity index 70% rename from Service/TaskService.php rename to Service/TaskLoggerService.php index 85c8e4b..b4fcb42 100644 --- a/Service/TaskService.php +++ b/Service/TaskLoggerService.php @@ -3,17 +3,15 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Service; use DigipolisGent\Domainator9k\CoreBundle\Entity\Task; -use DigipolisGent\Domainator9k\CoreBundle\Service\ProvisionService; use Doctrine\ORM\EntityManagerInterface; /** - * Class TaskService + * Class TaskLoggerService * * @package DigipolisGent\Domainator9k\CoreBundle\Service */ -class TaskService +class TaskLoggerService { - const LOG_TYPE_INFO = 'info'; const LOG_TYPE_WARNING = 'warning'; const LOG_TYPE_ERROR = 'error'; @@ -25,109 +23,17 @@ class TaskService * * @var EntityManagerInterface */ - private $entityManager; - - /** - * The provision service. - * - * @var ProvisionService - */ - private $provisionService; + protected $entityManager; /** * Class constructor. * * @param EntityManagerInterface $entityManager * The entity manager service. - * @param ProvisionService $provisionService - * The provision service. */ - public function __construct(EntityManagerInterface $entityManager, ProvisionService $provisionService) + public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; - $this->provisionService = $provisionService; - } - - /** - * Run a task. - * - * @param Task $task - * The task to run. - * - * @return boolean - * True when the task has been processed succesfully, false for any other - * status. - */ - public function run(Task $task) - { - if (!$task->isNew()) { - throw new \InvalidArgumentException(sprintf('Task "%s" cannot be restarted.', $task->getId())); - } - - // Set the task in progress. - $task->setInProgress(); - $this->entityManager->persist($task); - $this->entityManager->flush(); - - $this->provisionService->run($task); - - // Update the status. - if ($task->isInProgress()) { - $task->setProcessed(); - } - - // Add a log message or simply persist any changes. - switch (true) { - case $task->isProcessed(): - $this->addLogMessage($task, '', '', 0); - $this->addSuccessLogMessage($task, 'Task run completed.', 0); - break; - - case $task->isFailed(): - $this->addLogMessage($task, '', '', 0); - $this->addFailedLogMessage($task, 'Task run failed.', 0); - break; - - default: - $this->entityManager->persist($task); - $this->entityManager->flush(); - break; - } - - return $task->isProcessed(); - } - - /** - * Run the next task of the specified type. - * - * @param string $type - * The task type to run. - */ - public function runNext(string $type) - { - $task = $this->entityManager - ->getRepository(Task::class) - ->getNextTask($type); - - if ($task) { - $this->run($task); - } - } - - /** - * Cancel a task. - * - * @param Task $task - * The task to cancel. - */ - public function cancel(Task $task) - { - if ($task->getStatus() !== Task::STATUS_NEW) { - throw new \InvalidArgumentException(sprintf('Task %s cannot be cancelled.', $task->getId())); - } - - $task->setStatus(Task::STATUS_CANCEL); - $this->addInfoLogMessage($task, 'Task run cancelled.'); } /** diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php new file mode 100644 index 0000000..ce5cb7d --- /dev/null +++ b/Service/TaskRunnerService.php @@ -0,0 +1,212 @@ +buildProvisioners = $buildProvisioners; + $this->destroyProvisioners = $destroyProvisioners; + $this->entityManager = $entityManager; + $this->logger = $logger; + } + + /** + * Run a task. + * + * @param Task $task + * The task to run. + * + * @return boolean + * True when the task has been processed succesfully, false for any other + * status. + */ + public function run(Task $task) + { + if (!$task->isNew()) { + throw new \InvalidArgumentException(sprintf('Task "%s" cannot be restarted.', $task->getId())); + } + + // Set the task in progress. + $task->setInProgress(); + $this->entityManager->persist($task); + $this->entityManager->flush(); + + $this->runProvisioners($task); + + // Update the status. + if ($task->isInProgress()) { + $task->setProcessed(); + } + + // Add a log message or simply persist any changes. + switch (true) { + case $task->isProcessed(): + $this->logger->addLogMessage($task, '', '', 0); + $this->logger->addSuccessLogMessage($task, 'Task run completed.', 0); + break; + + case $task->isFailed(): + $this->logger->addLogMessage($task, '', '', 0); + $this->logger->addFailedLogMessage($task, 'Task run failed.', 0); + break; + + default: + $this->entityManager->persist($task); + $this->entityManager->flush(); + break; + } + + return $task->isProcessed(); + } + + /** + * Run all provisioners for a task. + * + * @param Task $task + * The task to run the provisioners for. + * + * @throws \InvalidArgumentException + * If the task type is not supported. + */ + protected function runProvisioners(Task $task) + { + switch ($task->getType()) { + case Task::TYPE_BUILD: + $this->build($task); + break; + + case Task::TYPE_DESTROY: + $this->destroy($task); + break; + + default: + throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); + } + } + + /** + * Run a build task. + * + * @param Task $task + * The build task. + */ + protected function build(Task $task) + { + foreach ($this->buildProvisioners as $provisioner) { + $provisioner->run($task); + if ($task->isFailed()) { + break; + } + } + } + + /** + * Run a destroy task. + * + * @param Task $task + * The destroy task. + */ + protected function destroy(Task $task) + { + foreach ($this->destroyProvisioners as $provisioner) { + $provisioner->run($task); + if ($task->isFailed()) { + break; + } + } + } + + /** + * Run the next task of the specified type. + * + * @param string $type + * The task type to run. + * + * @return boolean + * True on success, false on failure. + */ + public function runNext(string $type) + { + $task = $this->entityManager + ->getRepository(Task::class) + ->getNextTask($type); + + if ($task) { + return $this->run($task); + } + + return true; + } + + /** + * Cancel a task. + * + * @param Task $task + * The task to cancel. + */ + public function cancel(Task $task) + { + if ($task->getStatus() !== Task::STATUS_NEW) { + throw new \InvalidArgumentException(sprintf('Task %s cannot be cancelled.', $task->getId())); + } + + $task->setStatus(Task::STATUS_CANCEL); + $this->logger->addInfoLogMessage($task, 'Task run cancelled.'); + } + +} diff --git a/Tests/Service/ProvisionServiceTest.php b/Tests/Service/ProvisionServiceTest.php deleted file mode 100644 index ab6bd60..0000000 --- a/Tests/Service/ProvisionServiceTest.php +++ /dev/null @@ -1,159 +0,0 @@ -setType(Task::TYPE_BUILD); - - $buildProvisioners = []; - foreach (range(0, 5) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->once()) - ->method('run') - ->with($task) - ->willReturn(null); - $buildProvisioners[] = $mock; - } - $destroyProvisioners = []; - foreach (range(0, 5) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->never()) - ->method('run'); - $destroyProvisioners[] = $mock; - } - $provisionService = new ProvisionService($buildProvisioners, $destroyProvisioners); - $provisionService->run($task); - } - - public function testDestroy() - { - $task = new Task(); - $task->setType(Task::TYPE_DESTROY); - - $buildProvisioners = []; - foreach (range(0, 5) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->never()) - ->method('run'); - $buildProvisioners[] = $mock; - } - - $destroyProvisioners = []; - foreach (range(0, 5) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->once()) - ->method('run') - ->with($task) - ->willReturn(null); - $destroyProvisioners[] = $mock; - } - $provisionService = new ProvisionService($buildProvisioners, $destroyProvisioners); - $provisionService->run($task); - } - - public function testFailedBuild() - { - $task = new Task(); - $task->setType(Task::TYPE_BUILD); - - $buildProvisioners = []; - foreach (range(0, 3) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->once()) - ->method('run') - ->with($task) - ->willReturn(null); - $buildProvisioners[] = $mock; - } - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->once()) - ->method('run') - ->with($task) - ->willReturnCallback(function (Task $task) { - $task->setFailed(); - }); - $buildProvisioners[] = $mock; - - foreach (range(0, 2) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->never()) - ->method('run'); - $buildProvisioners[] = $mock; - } - $provisionService = new ProvisionService($buildProvisioners, []); - $provisionService->run($task); - } - - public function testFailedDestroy() - { - $task = new Task(); - $task->setType(Task::TYPE_DESTROY); - - $destroyProvisioners = []; - foreach (range(0, 3) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->once()) - ->method('run') - ->with($task) - ->willReturn(null); - $destroyProvisioners[] = $mock; - } - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->once()) - ->method('run') - ->with($task) - ->willReturnCallback(function (Task $task) { - $task->setFailed(); - }); - $destroyProvisioners[] = $mock; - - foreach (range(0, 2) as $i) { - $mock = $this->getMockBuilder(ProvisionerInterface::class) - ->getMock(); - $mock->expects($this->never()) - ->method('run'); - $destroyProvisioners[] = $mock; - } - $provisionService = new ProvisionService([], $destroyProvisioners); - $provisionService->run($task); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessageRegExp /Task type (.*) is not supported\./ - */ - public function testInvalidType() - { - $task = new Task(); - $task->setType(uniqid()); - $provisionService = new ProvisionService([], []); - $provisionService->run($task); - } -} diff --git a/Tests/Service/TaskRunnerServiceTest.php b/Tests/Service/TaskRunnerServiceTest.php new file mode 100644 index 0000000..1422146 --- /dev/null +++ b/Tests/Service/TaskRunnerServiceTest.php @@ -0,0 +1,370 @@ +task = new Task(); + $this->task->setType(Task::TYPE_BUILD); + $id = uniqid(); + $prop = new \ReflectionProperty($this->task, 'id'); + $prop->setAccessible(true); + $prop->setValue($this->task, $id); + + $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class) + ->getMock(); + + $this->provisionService = $this->getMockBuilder(ProvisionService::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->logger = $this->getMockBuilder(TaskLoggerService::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->entityManagerIndex = 0; + $this->provisionServiceIndex = 0; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Task "(.*)" cannot be restarted\./ + */ + public function testRunNotNew() + { + $this->task->setProcessed(); + $this->taskRunnerService = new TaskRunnerService([], [], $this->entityManager, $this->logger); + $this->taskRunnerService->run($this->task); + } + + public function testRunSuccessBuild() + { + $this->expectSuccessfulRun(); + $result = $this->taskRunnerService->run($this->task); + $this->assertTrue($result); + $this->assertTrue($this->task->isProcessed()); + } + + public function testRunSuccessDestroy() + { + $this->task->setType(Task::TYPE_DESTROY); + $this->expectSuccessfulRun(); + $result = $this->taskRunnerService->run($this->task); + $this->assertTrue($result); + $this->assertTrue($this->task->isProcessed()); + } + + public function testRunFailed() + { + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isInProgress(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->logger + ->expects($this->at(0)) + ->method('addLogMessage') + ->with($this->task, '', '', 0); + + $this->logger + ->expects($this->at(1)) + ->method('addFailedLogMessage') + ->with($this->task, 'Task run failed.', 0); + + $buildProvisioners = []; + foreach (range(0, 3) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($this->task) + ->willReturn(null); + $buildProvisioners[] = $mock; + } + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($this->task) + ->willReturnCallback(function (Task $task) { + $task->setFailed(); + }); + $buildProvisioners[] = $mock; + + foreach (range(0, 2) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->never()) + ->method('run'); + $buildProvisioners[] = $mock; + } + + $this->taskRunnerService = new TaskRunnerService( + $buildProvisioners, + [], + $this->entityManager, + $this->logger + ); + + $result = $this->taskRunnerService->run($this->task); + $this->assertFalse($result); + $this->assertTrue($this->task->isFailed()); + } + + public function testRunCancelled() + { + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isInProgress(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isCancelled(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $buildProvisioners = []; + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($this->task) + ->willReturn(null); + $buildProvisioners[] = $mock; + } + + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($this->task) + ->willReturnCallback( + function (Task $task) + { + $task->setCancelled(); + } + ); + $buildProvisioners[] = $mock; + + $this->taskRunnerService = new TaskRunnerService( + $buildProvisioners, + [], + $this->entityManager, + $this->logger + ); + $result = $this->taskRunnerService->run($this->task); + $this->assertFalse($result); + $this->assertTrue($this->task->isCancelled()); + } + + public function testRunNext() + { + $repository = $this->getMockBuilder(TaskRepository::class) + ->disableOriginalConstructor() + ->getMock(); + $repository->expects($this->at(0)) + ->method('getNextTask') + ->with(Task::TYPE_BUILD) + ->willReturn($this->task); + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('getRepository') + ->with(Task::class) + ->willReturn($repository); + $this->expectSuccessfulRun(); + $result = $this->taskRunnerService->runNext(Task::TYPE_BUILD); + $this->assertTrue($result); + $this->assertTrue($this->task->isProcessed()); + } + + public function testCancel() + { + $this->logger + ->expects($this->at(0)) + ->method('addInfoLogMessage') + ->with($this->task, 'Task run cancelled.'); + $this->taskRunnerService = new TaskRunnerService( + [], + [], + $this->entityManager, + $this->logger + ); + $this->taskRunnerService->cancel($this->task); + + $this->assertTrue($this->task->isCancelled()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Task (.*) cannot be cancelled\./ + */ + public function testCancelRunning() + { + $this->task->setInProgress(); + $this->taskRunnerService = new TaskRunnerService( + [], + [], + $this->entityManager, + $this->logger + ); + $this->taskRunnerService->cancel($this->task); + } + + protected function expectSuccessfulRun() + { + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('persist') + ->with($this->callback( + function (Task $task) + { + return $task->isInProgress(); + } + )); + + $this->entityManager + ->expects($this->at($this->entityManagerIndex++)) + ->method('flush'); + + $this->logger + ->expects($this->at(0)) + ->method('addLogMessage') + ->with($this->task, '', '', 0); + + $this->logger + ->expects($this->at(1)) + ->method('addSuccessLogMessage') + ->with($this->task, 'Task run completed.', 0); + + $buildProvisioners = []; + $destroyProvisioners = []; + switch ($this->task->getType()) { + case Task::TYPE_BUILD: + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($this->task) + ->willReturn(null); + $buildProvisioners[] = $mock; + } + + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->never()) + ->method('run'); + $destroyProvisioners[] = $mock; + } + break; + case Task::TYPE_DESTROY: + + $buildProvisioners = []; + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->never()) + ->method('run'); + $buildProvisioners[] = $mock; + } + + $destroyProvisioners = []; + foreach (range(0, 5) as $i) { + $mock = $this->getMockBuilder(ProvisionerInterface::class) + ->getMock(); + $mock->expects($this->once()) + ->method('run') + ->with($this->task) + ->willReturn(null); + $destroyProvisioners[] = $mock; + } + break; + } + + $this->taskRunnerService = new TaskRunnerService( + $buildProvisioners, + $destroyProvisioners, + $this->entityManager, + $this->logger + ); + } +} diff --git a/Tests/Service/TaskServiceTest.php b/Tests/Service/TaskServiceTest.php deleted file mode 100644 index 3dc4053..0000000 --- a/Tests/Service/TaskServiceTest.php +++ /dev/null @@ -1,282 +0,0 @@ -task = new Task(); - $this->task->setType(Task::TYPE_BUILD); - $id = uniqid(); - $prop = new \ReflectionProperty($this->task, 'id'); - $prop->setAccessible(true); - $prop->setValue($this->task, $id); - - $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class) - ->getMock(); - - $this->provisionService = $this->getMockBuilder(ProvisionService::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->entityManagerIndex = 0; - $this->provisionServiceIndex = 0; - - $this->taskService = new TaskService($this->entityManager, $this->provisionService); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessageRegExp /Task "(.*)" cannot be restarted\./ - */ - public function testRunNotNew() - { - $this->task->setProcessed(); - $this->taskService->run($this->task); - } - - public function testRunSuccess() - { - $this->expectSuccessfulRun(); - - $this->taskService->run($this->task); - } - - public function testRunFailed() - { - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) - { - return $task->isInProgress(); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - - $this->provisionService - ->expects($this->at($this->provisionServiceIndex++)) - ->method('run') - ->with($this->callback( - function (Task $task) { - return $task->isInProgress(); - } - )) - ->willReturnCallback(function (Task $task) { - $task->setFailed(); - }); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) { - return $task->isFailed(); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) { - return $task->isFailed() && (strpos($task->getLog(), 'Task run failed.') !== false); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - - $this->taskService->run($this->task); - } - - public function testRunCancelled() - { - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) - { - return $task->isInProgress(); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - - $this->provisionService - ->expects($this->at($this->provisionServiceIndex++)) - ->method('run') - ->with($this->callback( - function (Task $task) { - return $task->isInProgress(); - } - )) - ->willReturnCallback(function (Task $task) { - $task->setCancelled(); - }); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) { - return $task->isCancelled() && is_null($task->getLog()); - } - )); - - $this->taskService->run($this->task); - } - - public function testRunNext() - { - $repository = $this->getMockBuilder(TaskRepository::class) - ->disableOriginalConstructor() - ->getMock(); - $repository->expects($this->at(0)) - ->method('getNextTask') - ->with(Task::TYPE_BUILD) - ->willReturn($this->task); - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('getRepository') - ->with(Task::class) - ->willReturn($repository); - $this->expectSuccessfulRun(); - $this->taskService->runNext(Task::TYPE_BUILD); - } - - public function testCancel() - { - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) - { - return $task->isCancelled() && (strpos($task->getLog(), 'Task run cancelled.') !== false); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - - $this->taskService->cancel($this->task); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessageRegExp /Task (.*) cannot be cancelled\./ - */ - public function testCancelRunning() - { - $this->task->setInProgress(); - $this->taskService->cancel($this->task); - } - - protected function expectSuccessfulRun() - { - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) - { - return $task->isInProgress(); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - - $this->provisionService - ->expects($this->at($this->provisionServiceIndex++)) - ->method('run') - ->with($this->callback( - function (Task $task) { - return $task->isInProgress(); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) { - return $task->isProcessed(); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('persist') - ->with($this->callback( - function (Task $task) { - return $task->isProcessed() && (strpos($task->getLog(), 'Task run completed.') !== FALSE); - } - )); - - $this->entityManager - ->expects($this->at($this->entityManagerIndex++)) - ->method('flush'); - } -} From 1a89bf8f2a177958bd8bcab7cb569149b314e737 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 16:34:12 +0200 Subject: [PATCH 071/104] WEBDOM-391: [WIP] Fix codeclimate issues. --- Service/TaskRunnerService.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php index ce5cb7d..6b74fec 100644 --- a/Service/TaskRunnerService.php +++ b/Service/TaskRunnerService.php @@ -57,8 +57,7 @@ public function __construct( iterable $destroyProvisioners, EntityManagerInterface $entityManager, TaskLoggerService $logger - ) - { + ) { $this->buildProvisioners = $buildProvisioners; $this->destroyProvisioners = $destroyProvisioners; $this->entityManager = $entityManager; From 3e11997d7b76fb7e43185b3f336bb51be152ffea Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 16:39:50 +0200 Subject: [PATCH 072/104] WEBDOM-391: [WIP] Fix autowiring. --- Resources/config/services.yml | 3 ++- Service/TaskRunnerService.php | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 69a1b09..ee2cc6c 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -12,7 +12,8 @@ services: arguments: [ !tagged domainator.provisioner.build, !tagged domainator.provisioner.destroy, - '@doctrine.orm.entity_manager' + '@doctrine.orm.entity_manager', + '@DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService' ] DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationEnvironmentFormType: tags: [form.type] diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php index 6b74fec..fb64c0e 100644 --- a/Service/TaskRunnerService.php +++ b/Service/TaskRunnerService.php @@ -207,5 +207,4 @@ public function cancel(Task $task) $task->setStatus(Task::STATUS_CANCEL); $this->logger->addInfoLogMessage($task, 'Task run cancelled.'); } - } From 284b51a255fa275c77e2b462f0017c4d8678cdf5 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 17:02:57 +0200 Subject: [PATCH 073/104] WEBDOM-391: Handle exceptions thrown while running a task. --- Exception/LoggedException.php | 6 ++++ Provisioner/ProvisionerInterface.php | 1 + Resources/config/services.yml | 12 ++++---- Service/TaskRunnerService.php | 46 +++++++++------------------- 4 files changed, 28 insertions(+), 37 deletions(-) create mode 100644 Exception/LoggedException.php diff --git a/Exception/LoggedException.php b/Exception/LoggedException.php new file mode 100644 index 0000000..7af3ed3 --- /dev/null +++ b/Exception/LoggedException.php @@ -0,0 +1,6 @@ +getType()) { case Task::TYPE_BUILD: - $this->build($task); + $provisioners = $this->buildProvisioners; break; case Task::TYPE_DESTROY: - $this->destroy($task); + $provisioners = $this->destroyProvisioners; break; default: throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType())); } - } - - /** - * Run a build task. - * - * @param Task $task - * The build task. - */ - protected function build(Task $task) - { - foreach ($this->buildProvisioners as $provisioner) { - $provisioner->run($task); - if ($task->isFailed()) { - break; + try { + foreach ($provisioners as $provisioner) { + $provisioner->run($task); + if ($task->isFailed()) { + break; + } } - } - } - - /** - * Run a destroy task. - * - * @param Task $task - * The destroy task. - */ - protected function destroy(Task $task) - { - foreach ($this->destroyProvisioners as $provisioner) { - $provisioner->run($task); - if ($task->isFailed()) { - break; + } catch (\Exception $ex) { + if (!($ex instanceof LoggedException)) { + $task->setFailed(); + $this->logger->addErrorLogMessage($task, $ex->getMessage(), 2); + $this->logger->addFailedLogMessage($task, sprintf('Provisioner %s failed.', $provisioner->getName())); } } } From a63d1ba8f583bef1a914bfa2f4a2229ca5eff62f Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 8 Jun 2018 17:06:12 +0200 Subject: [PATCH 074/104] WEBDOM-391: Fix codeclimate issues. --- Exception/LoggedException.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Exception/LoggedException.php b/Exception/LoggedException.php index 7af3ed3..ecc9af8 100644 --- a/Exception/LoggedException.php +++ b/Exception/LoggedException.php @@ -2,5 +2,6 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Exception; -class LoggedException extends \Exception { +class LoggedException extends \Exception +{ } From bbd568d54e10491140724e72ebc65420d0935e67 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 11 Jun 2018 12:25:13 +0200 Subject: [PATCH 075/104] WEBDOM-391: Move escapeLog method to the task logger service. --- Service/TaskLoggerService.php | 114 ++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/Service/TaskLoggerService.php b/Service/TaskLoggerService.php index b4fcb42..f3da140 100644 --- a/Service/TaskLoggerService.php +++ b/Service/TaskLoggerService.php @@ -235,4 +235,118 @@ protected function indentText(string $text, int $indent) return $matches[1] . $suffix; }, $text); } + + /** + * Generate an HTML safe task log. + * + * @param string $log + * The task log. + * + * @return string + * The escaped log. + */ + public function escapeLog(string $log): string + { + // Default HTML escaping. + $log = htmlspecialchars($log, ENT_QUOTES, 'UTF-8', false); + $log = str_replace(["\r\n", "\r"], "\n", $log); + + // Make titles bold. + $log = preg_replace('/^(\t*)### (.+) ###$/m', '$1$2', $log); + + // Count the number of lines. + $lineCount = substr_count($log, "\n") + 1; + + // Get the line number width. + $lineNumberWidth = \strlen($lineCount); + + // Wrap all lines + $lineNumber = 0; + $prevIndents = []; + $log = (string) preg_replace_callback( + '/^(\t*)(?:(.+?)(?: \[(warning|error|success|failed)\])?)?$/m', + function ($matches) use (&$lineNumber, &$prevIndents, $lineCount, $lineNumberWidth) { + if (isset($matches[2])) { + $indent = \strlen($matches[1]); + $line = $matches[2]; + } else { + $indent = $prevIndents[0] ?? 0; + $line = ''; + } + + $status = $matches[3] ?? null; + + // Apply the message wrapper with indentation. + $line = sprintf( + '
%s
', + 'message message--indent-' . $indent, + $indent * 1.5, + $line + ); + + // Add the line number. + $lineNumber++; + $line = sprintf( + '
%' . $lineNumberWidth . 's
%s', + 'number number--' . $lineNumber, + $lineNumber, + $line + ); + + // Add the line status. + if ($status !== null) { + $line = sprintf( + '%s
[%s]
', + $line, + 'status status--' . $status, + $status + ); + } + + // Wrap the whole line. + $class = 'line line--' . $lineNumber; + + if ($lineNumber === 1) { + $class .= ' line--first'; + } elseif ($lineNumber === $lineCount) { + $class .= ' line--last'; + } + + if ($status !== null) { + $class .= ' line--status-' . $status; + } + + $line = sprintf( + '
%s
', + $class, + $line + ); + + if (!$prevIndents || $indent > $prevIndents[0]) { + // Start a new indentation group. + $line = sprintf( + '
%s', + 'group group--indent-' . $indent . ' group--number-' . $lineNumber, + $line + ); + array_unshift($prevIndents, $indent); + } elseif ($indent < $prevIndents[0]) { + // Close the previous groups. + do { + $line = '
' . $line; + array_shift($prevIndents); + } while ($indent < $prevIndents[0]); + } + + return $line; + }, + $log + ); + + if ($prevIndents) { + $log .= str_repeat('', \count($prevIndents)); + } + + return $log; + } } From 8301e46366d44c63606fff5e029ef23b044cd03a Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 11 Jun 2018 12:34:58 +0200 Subject: [PATCH 076/104] WEBDOM-391: Fixed review remarks. --- Service/TaskRunnerService.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php index 8ed05db..f12e3cf 100644 --- a/Service/TaskRunnerService.php +++ b/Service/TaskRunnerService.php @@ -146,10 +146,11 @@ protected function runProvisioners(Task $task) } } } catch (\Exception $ex) { + $task->setFailed(); if (!($ex instanceof LoggedException)) { - $task->setFailed(); - $this->logger->addErrorLogMessage($task, $ex->getMessage(), 2); - $this->logger->addFailedLogMessage($task, sprintf('Provisioner %s failed.', $provisioner->getName())); + $this->logger + ->addErrorLogMessage($task, $ex->getMessage(), 2) + ->addFailedLogMessage($task, sprintf('Provisioner %s failed.', $provisioner->getName())); } } } From 0408d8ad2d2b984166196e0150b0d1fbf121f67a Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 11 Jun 2018 13:14:09 +0200 Subject: [PATCH 077/104] Fix services.yml. --- Resources/config/services.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 0e5757f..6e5381b 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -9,12 +9,12 @@ services: DigipolisGent\Domainator9k\CoreBundle\Service\TemplateService: DigipolisGent\Domainator9k\CoreBundle\Service\TokenService: DigipolisGent\Domainator9k\CoreBundle\Service\TaskRunnerService: - arguments: [ - !tagged domainator.provisioner.build, - !tagged domainator.provisioner.destroy, - '@doctrine.orm.entity_manager', - '@DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService' - ] + arguments: + - !tagged domainator.provisioner.build + - !tagged domainator.provisioner.destroy + - '@doctrine.orm.entity_manager' + - '@DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService' + DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationEnvironmentFormType: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\EnvironmentFormType: From 3a46d5efa5e5a6f82d67918c88f07908cfd5e033 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 11 Jun 2018 14:39:04 +0200 Subject: [PATCH 078/104] Create a setter for the task on the provisioners. --- Provisioner/AbstractProvisioner.php | 33 +++++++++++++++++++++ Provisioner/ProvisionerInterface.php | 3 +- Service/TaskRunnerService.php | 3 +- Tests/Service/TaskRunnerServiceTest.php | 38 ++++++++++++++++++------- 4 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 Provisioner/AbstractProvisioner.php diff --git a/Provisioner/AbstractProvisioner.php b/Provisioner/AbstractProvisioner.php new file mode 100644 index 0000000..8e33ed8 --- /dev/null +++ b/Provisioner/AbstractProvisioner.php @@ -0,0 +1,33 @@ +task = $task; + } + + public final function run() + { + if (!($this->task instanceof Task)) { + throw new \LogicException('A task must be set before running a provisioner.'); + } + $this->doRun(); + } + + abstract protected function doRun(); +} diff --git a/Provisioner/ProvisionerInterface.php b/Provisioner/ProvisionerInterface.php index 6e31814..f98e63a 100644 --- a/Provisioner/ProvisionerInterface.php +++ b/Provisioner/ProvisionerInterface.php @@ -6,6 +6,7 @@ interface ProvisionerInterface { - public function run(Task $task); + public function setTask(Task $task); + public function run(); public function getName(); } diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php index f12e3cf..e848eaa 100644 --- a/Service/TaskRunnerService.php +++ b/Service/TaskRunnerService.php @@ -140,7 +140,8 @@ protected function runProvisioners(Task $task) } try { foreach ($provisioners as $provisioner) { - $provisioner->run($task); + $provisioner->setTask($task); + $provisioner->run(); if ($task->isFailed()) { break; } diff --git a/Tests/Service/TaskRunnerServiceTest.php b/Tests/Service/TaskRunnerServiceTest.php index 1422146..6b3dcf0 100644 --- a/Tests/Service/TaskRunnerServiceTest.php +++ b/Tests/Service/TaskRunnerServiceTest.php @@ -135,18 +135,23 @@ function (Task $task) $mock = $this->getMockBuilder(ProvisionerInterface::class) ->getMock(); $mock->expects($this->once()) - ->method('run') + ->method('setTask') ->with($this->task) ->willReturn(null); + $mock->expects($this->once()) + ->method('run') + ->willReturn(null); $buildProvisioners[] = $mock; } $mock = $this->getMockBuilder(ProvisionerInterface::class) ->getMock(); + $mock->expects($this->once()) + ->method('setTask') + ->with($this->task); $mock->expects($this->once()) ->method('run') - ->with($this->task) - ->willReturnCallback(function (Task $task) { - $task->setFailed(); + ->willReturnCallback(function () { + $this->task->setFailed(); }); $buildProvisioners[] = $mock; @@ -205,21 +210,27 @@ function (Task $task) $mock = $this->getMockBuilder(ProvisionerInterface::class) ->getMock(); $mock->expects($this->once()) - ->method('run') + ->method('setTask') ->with($this->task) ->willReturn(null); + $mock->expects($this->once()) + ->method('run') + ->willReturn(null); $buildProvisioners[] = $mock; } $mock = $this->getMockBuilder(ProvisionerInterface::class) ->getMock(); $mock->expects($this->once()) - ->method('run') + ->method('setTask') ->with($this->task) + ->willReturn(null); + $mock->expects($this->once()) + ->method('run') ->willReturnCallback( - function (Task $task) + function () { - $task->setCancelled(); + $this->task->setCancelled(); } ); $buildProvisioners[] = $mock; @@ -322,9 +333,12 @@ function (Task $task) $mock = $this->getMockBuilder(ProvisionerInterface::class) ->getMock(); $mock->expects($this->once()) - ->method('run') + ->method('setTask') ->with($this->task) ->willReturn(null); + $mock->expects($this->once()) + ->method('run') + ->willReturn(null); $buildProvisioners[] = $mock; } @@ -352,9 +366,13 @@ function (Task $task) $mock = $this->getMockBuilder(ProvisionerInterface::class) ->getMock(); $mock->expects($this->once()) - ->method('run') + ->method('setTask') ->with($this->task) ->willReturn(null); + $mock->expects($this->once()) + ->method('run') + ->with() + ->willReturn(null); $destroyProvisioners[] = $mock; } break; From 5899ec75c243452067b22dd82ca8b4bb2dfaa379 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 12 Jun 2018 09:54:05 +0200 Subject: [PATCH 079/104] WEBDOM-389: Allow selective provisioning. --- Entity/Task.php | 21 +++++++++ Form/Type/TaskFormType.php | 81 +++++++++++++++++++++++++++++++++++ Resources/config/services.yml | 2 + Service/TaskRunnerService.php | 20 +++++++++ 4 files changed, 124 insertions(+) create mode 100644 Form/Type/TaskFormType.php diff --git a/Entity/Task.php b/Entity/Task.php index 0838f72..c4b0ff1 100644 --- a/Entity/Task.php +++ b/Entity/Task.php @@ -52,6 +52,12 @@ class Task */ protected $created; + /** + * @var string[] + * @ORM\Column(name="provisioners", type="simple_array", nullable=true) + */ + protected $provisioners; + /** * @var string * @ORM\Column(name="log", type="text", nullable=true) @@ -123,6 +129,21 @@ public function getCreated(): DateTime return $this->created; } + /** + * @return string[] + */ + public function getProvisioners() + { + return $this->provisioners; + } + + /** + * @param string[] $provisioners + */ + public function setProvisioners($provisioners) { + $this->provisioners = $provisioners; + } + /** * Gets the log. * diff --git a/Form/Type/TaskFormType.php b/Form/Type/TaskFormType.php new file mode 100644 index 0000000..d1e2434 --- /dev/null +++ b/Form/Type/TaskFormType.php @@ -0,0 +1,81 @@ +formService = $formService; + $this->taskRunnerService = $taskRunnerService; + } + + /** + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + parent::buildForm($builder, $options); + $choices = []; + $provisioners = []; + switch ($options['type']) { + case Task::TYPE_BUILD: + $provisioners = $this->taskRunnerService->getBuildProvisioners(); + break; + + case Task::TYPE_DESTROY: + $provisioners = $this->taskRunnerService->getDestroyProvisioners(); + break; + } + + foreach ($provisioners as $provisioner) { + $choices[$provisioner->getName()] = get_class($provisioner); + } + $builder->add('provisioners', ChoiceType::class, [ + 'expanded' => true, + 'multiple' => true, + 'required' => false, + 'choices' => $choices, + 'label' => 'Limit to following provisioners (selecting none will run all provisioners)' + ]); + $builder->addEventSubscriber(new SettingFormListener($this->formService)); + } + + /** + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + $resolver->setDefault('data_class', Task::class); + $resolver->setDefault('type', Task::TYPE_BUILD); + } +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 6e5381b..590eb84 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -23,6 +23,8 @@ services: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\TokenFormType: tags: [form.type] + DigipolisGent\Domainator9k\CoreBundle\Form\Type\TaskFormType: + tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationTypeEnvironmentFormType: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Twig\TemplateHelpExtension: diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php index e848eaa..9d69ddd 100644 --- a/Service/TaskRunnerService.php +++ b/Service/TaskRunnerService.php @@ -140,6 +140,9 @@ protected function runProvisioners(Task $task) } try { foreach ($provisioners as $provisioner) { + if ($task->getProvisioners() && !in_array(get_class($provisioner), $task->getProvisioners())) { + continue; + } $provisioner->setTask($task); $provisioner->run(); if ($task->isFailed()) { @@ -193,4 +196,21 @@ public function cancel(Task $task) $task->setStatus(Task::STATUS_CANCEL); $this->logger->addInfoLogMessage($task, 'Task run cancelled.'); } + + /** + * @return ProvisionerInterface[] + */ + public function getBuildProvisioners() + { + return $this->buildProvisioners; + } + + /** + * @return ProvisionerInterface[] + */ + public function getDestroyProvisioners() + { + return $this->destroyProvisioners; + } + } From 061d6c588d0f03c9a9d1dc9ce39896bec4cb1199 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 12 Jun 2018 11:55:34 +0200 Subject: [PATCH 080/104] WEBDOM-389: Added tests for new form type. --- Form/Type/TaskFormType.php | 2 +- Tests/Form/Type/AbstractFormTypeTest.php | 10 +++ Tests/Form/Type/TaskFormTypeTest.php | 81 ++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 Tests/Form/Type/TaskFormTypeTest.php diff --git a/Form/Type/TaskFormType.php b/Form/Type/TaskFormType.php index d1e2434..54d6e87 100644 --- a/Form/Type/TaskFormType.php +++ b/Form/Type/TaskFormType.php @@ -64,7 +64,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'multiple' => true, 'required' => false, 'choices' => $choices, - 'label' => 'Limit to following provisioners (selecting none will run all provisioners)' + 'label' => 'Limit to following provisioners (selecting none will run all provisioners)', ]); $builder->addEventSubscriber(new SettingFormListener($this->formService)); } diff --git a/Tests/Form/Type/AbstractFormTypeTest.php b/Tests/Form/Type/AbstractFormTypeTest.php index 81e0be3..51556b8 100644 --- a/Tests/Form/Type/AbstractFormTypeTest.php +++ b/Tests/Form/Type/AbstractFormTypeTest.php @@ -3,6 +3,7 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Tests\Form\Type; use DigipolisGent\SettingBundle\Service\FormService; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -10,6 +11,9 @@ abstract class AbstractFormTypeTest extends TestCase { + /** + * @return MockObject + */ protected function getFormBuilderMock() { $mock = $this @@ -20,6 +24,9 @@ protected function getFormBuilderMock() return $mock; } + /** + * @return MockObject + */ protected function getOptionsResolverMock() { $mock = $this @@ -30,6 +37,9 @@ protected function getOptionsResolverMock() return $mock; } + /** + * @return MockObject + */ protected function getFormServiceMock() { $mock = $this diff --git a/Tests/Form/Type/TaskFormTypeTest.php b/Tests/Form/Type/TaskFormTypeTest.php new file mode 100644 index 0000000..d5aed15 --- /dev/null +++ b/Tests/Form/Type/TaskFormTypeTest.php @@ -0,0 +1,81 @@ +getOptionsResolverMock(); + + $optionsResolver + ->expects($this->at(0)) + ->method('setDefault') + ->with('data_class', Task::class); + $optionsResolver + ->expects($this->at(1)) + ->method('setDefault') + ->with('type', Task::TYPE_BUILD); + + $taskRunnerService = $this->getMockBuilder(TaskRunnerService::class) + ->disableOriginalConstructor() + ->getMock(); + + $formType = new TaskFormType($this->getFormServiceMock(), $taskRunnerService); + $formType->configureOptions($optionsResolver); + } + + public function testBuildForm() + { + $formBuilder = $this->getFormBuilderMock(); + + $taskRunnerService = $this->getMockBuilder(TaskRunnerService::class) + ->disableOriginalConstructor() + ->getMock(); + + $provisioners = []; + $choices = []; + foreach(range(0,5) as $index) { + $mock = $this->getMockBuilder(ProvisionerInterface::class)->getMock(); + $name = 'Provisioner' . $index; + $mock->expects($this->once())->method('getName')->willReturn($name); + $provisioners[] = $mock; + $choices[$name] = get_class($mock); + } + + $taskRunnerService + ->expects($this->once()) + ->method('getBuildProvisioners') + ->willReturn($provisioners); + + $formBuilder + ->expects($this->at(0)) + ->method('add') + ->with( + 'provisioners', + ChoiceType::class, + [ + 'expanded' => true, + 'multiple' => true, + 'required' => false, + 'choices' => $choices, + 'label' => 'Limit to following provisioners (selecting none will run all provisioners)', + ] + ); + + $formBuilder + ->expects($this->at(1)) + ->method('addEventSubscriber'); + + $formType = new TaskFormType($this->getFormServiceMock(), $taskRunnerService); + $formType->buildForm($formBuilder, ['type' => Task::TYPE_BUILD]); + } +} From 42128827c6069f5e55363360545e1f25cc3a41c0 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 12 Jun 2018 11:59:17 +0200 Subject: [PATCH 081/104] WEBDOM-389: Fix codeclimate issues. --- Entity/Task.php | 3 ++- Service/TaskRunnerService.php | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Entity/Task.php b/Entity/Task.php index c4b0ff1..3f0d1c8 100644 --- a/Entity/Task.php +++ b/Entity/Task.php @@ -140,7 +140,8 @@ public function getProvisioners() /** * @param string[] $provisioners */ - public function setProvisioners($provisioners) { + public function setProvisioners($provisioners) + { $this->provisioners = $provisioners; } diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php index 9d69ddd..60d3adf 100644 --- a/Service/TaskRunnerService.php +++ b/Service/TaskRunnerService.php @@ -212,5 +212,4 @@ public function getDestroyProvisioners() { return $this->destroyProvisioners; } - } From 3b3334105f88ca1a0f354ba07e9797908a6dba54 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 15 Jun 2018 09:55:56 +0200 Subject: [PATCH 082/104] WEBDOM-392: Added getConfig method to SettingsImplementationTrait. --- Entity/ApplicationEnvironment.php | 11 ----------- Entity/Environment.php | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/Entity/ApplicationEnvironment.php b/Entity/ApplicationEnvironment.php index df91eb4..d596e93 100644 --- a/Entity/ApplicationEnvironment.php +++ b/Entity/ApplicationEnvironment.php @@ -250,15 +250,4 @@ public function getWorkerServerIp(): string return end($servers)->getHost(); } - - public function getConfig(string $key): ?string - { - foreach ($this->getSettingDataValues() as $settingDataValue) { - if ($settingDataValue->getSettingDataType()->getKey() == $key) { - return $settingDataValue->getValue(); - } - } - - return ''; - } } diff --git a/Entity/Environment.php b/Entity/Environment.php index daa7e55..53a77e1 100644 --- a/Entity/Environment.php +++ b/Entity/Environment.php @@ -213,17 +213,6 @@ public function setGitRef(string $gitRef) $this->gitRef = $gitRef; } - public function getConfig(string $key): ?string - { - foreach ($this->getSettingDataValues() as $settingDataValue) { - if ($settingDataValue->getSettingDataType()->getKey() == $key) { - return $settingDataValue->getValue(); - } - } - - return ''; - } - /** * @return int */ From 22807f854728d07ccf115014b0f16512701d4453 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 15 Jun 2018 10:43:53 +0200 Subject: [PATCH 083/104] WEBDOM-392: Fix tests. --- Tests/Entity/AbstractApplicationTest.php | 1 + Tests/Entity/ApplicationEnvironmentTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Tests/Entity/AbstractApplicationTest.php b/Tests/Entity/AbstractApplicationTest.php index 56aa8c4..3ea35aa 100644 --- a/Tests/Entity/AbstractApplicationTest.php +++ b/Tests/Entity/AbstractApplicationTest.php @@ -18,6 +18,7 @@ public function testGetTemplateReplacements() 'name()' => 'getName()', 'nameCanonical()' => 'getNameCanonical()', 'gitRepo()' => 'getGitRepo()', + 'config(key)' => 'getConfig(key)', 'applicationEnvironmentDatabaseName(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getDatabaseName()', 'applicationEnvironmentEnvironmentName(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getEnvironmentName()', 'applicationEnvironmentEnvironmentGitRef(name)' => 'getApplicationEnvironmentByEnvironmentName(name).getEnvironment().getGitRef()', diff --git a/Tests/Entity/ApplicationEnvironmentTest.php b/Tests/Entity/ApplicationEnvironmentTest.php index 7488c61..4d93f4f 100644 --- a/Tests/Entity/ApplicationEnvironmentTest.php +++ b/Tests/Entity/ApplicationEnvironmentTest.php @@ -38,6 +38,7 @@ public function testTemplateReplacements() 'databasePassword()' => 'getDatabasePassword()', 'gitRef()' => 'getGitRef()', 'domain()' => 'getDomain()', + 'applicationConfig(key)' => 'getApplication().getConfig(key)' ]; $this->assertEquals($expected, ApplicationEnvironment::getTemplateReplacements()); From 205c747a8a66c1d2732c2a4fb29420aeaa94554f Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 15 Jun 2018 16:31:19 +0200 Subject: [PATCH 084/104] Fix service name --- Command/AbstractCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Command/AbstractCommand.php b/Command/AbstractCommand.php index 317a748..9a19451 100644 --- a/Command/AbstractCommand.php +++ b/Command/AbstractCommand.php @@ -2,7 +2,7 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Command; -use DigipolisGent\Domainator9k\CoreBundle\Service\TaskService; +use DigipolisGent\Domainator9k\CoreBundle\Service\TaskRunnerService; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; abstract class AbstractCommand extends ContainerAwareCommand @@ -17,7 +17,7 @@ abstract class AbstractCommand extends ContainerAwareCommand protected function runNextTask(string $type) { $this->getContainer() - ->get(TaskService::class) + ->get(TaskRunnerService::class) ->runNext($type); } } From 02fa5596d57a553fef33150afca94b3a62bbc243 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 19 Jun 2018 11:01:48 +0200 Subject: [PATCH 085/104] WEBDOM-390: Provide a basic framework to support clearing cache during provisioning. --- .travis.yml | 3 +- CLI/CliFactoryInterface.php | 8 ++ CLI/CliInterface.php | 8 ++ CLI/DefaultCliFactory.php | 53 +++++++++++ CLI/RemoteCli.php | 29 ++++++ CacheClearer/CacheClearerInterface.php | 10 ++ .../CacheClearProviderCompilerPass.php | 32 +++++++ .../CliFactoryProviderCompilerPass.php | 33 +++++++ Exception/NoCacheClearerFoundException.php | 7 ++ Exception/NoCliFactoryFoundException.php | 7 ++ Provider/CacheClearProvider.php | 34 +++++++ Provider/CliFactoryProvider.php | 50 ++++++++++ Provisioner/CacheClearBuildProvisioner.php | 56 ++++++++++++ Resources/config/services.yml | 12 +++ Tests/CLI/DefaultCliFactoryTest.php | 91 +++++++++++++++++++ Tests/CLI/RemoteCliTest.php | 23 +++++ .../CacheClearProviderCompilerPassTest.php | 49 ++++++++++ .../CliFactoryProviderCompilerPassTest.php | 49 ++++++++++ Tests/Provider/CacheClearProviderTest.php | 35 +++++++ Tests/Provider/CliFactoryProviderTest.php | 49 ++++++++++ .../CacheClearBuildProvisionerTest.php | 55 +++++++++++ phpunit.xml | 9 +- 22 files changed, 700 insertions(+), 2 deletions(-) create mode 100644 CLI/CliFactoryInterface.php create mode 100644 CLI/CliInterface.php create mode 100644 CLI/DefaultCliFactory.php create mode 100644 CLI/RemoteCli.php create mode 100644 CacheClearer/CacheClearerInterface.php create mode 100644 DependencyInjection/Compiler/CacheClearProviderCompilerPass.php create mode 100644 DependencyInjection/Compiler/CliFactoryProviderCompilerPass.php create mode 100644 Exception/NoCacheClearerFoundException.php create mode 100644 Exception/NoCliFactoryFoundException.php create mode 100644 Provider/CacheClearProvider.php create mode 100644 Provider/CliFactoryProvider.php create mode 100644 Provisioner/CacheClearBuildProvisioner.php create mode 100644 Tests/CLI/DefaultCliFactoryTest.php create mode 100644 Tests/CLI/RemoteCliTest.php create mode 100644 Tests/DependencyInjection/Compiler/CacheClearProviderCompilerPassTest.php create mode 100644 Tests/DependencyInjection/Compiler/CliFactoryProviderCompilerPassTest.php create mode 100644 Tests/Provider/CacheClearProviderTest.php create mode 100644 Tests/Provider/CliFactoryProviderTest.php create mode 100644 Tests/Provisioner/CacheClearBuildProvisionerTest.php diff --git a/.travis.yml b/.travis.yml index 7a73aa6..28235e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,10 @@ before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build + - cat /dev/zero | ssh-keygen -q -N "" script: - vendor/bin/phpunit --disallow-test-output --strict-coverage -d error_reporting=-1 --coverage-clover=build/logs/clover.xml Tests after_script: - - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT \ No newline at end of file + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT diff --git a/CLI/CliFactoryInterface.php b/CLI/CliFactoryInterface.php new file mode 100644 index 0000000..2a2b4fb --- /dev/null +++ b/CLI/CliFactoryInterface.php @@ -0,0 +1,8 @@ +getEnvironment(); + /** @var AbstractApplication $application */ + $application = $applicationEnvironment->getApplication(); + + /** @var VirtualServer[] $servers */ + $servers = $environment->getVirtualServers(); + + foreach ($servers as $server) { + if (!$server->isTaskServer()) { + continue; + } + + $user = $application->getNameCanonical(); + $keyLocation = rtrim(Path::getHomeDirectory(), '/') . '/.ssh/id_rsa'; + + $ssh = new SSH2($server->getHost(), $server->getPort()); + $key = new RSA(); + $key->loadKey(file_get_contents($keyLocation)); + + if (!$ssh->login($user, $key)) { + throw new \Exception('SSH login failed.'); + } + return new RemoteCli($ssh); + } + return null; + } +} diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php new file mode 100644 index 0000000..28d0faa --- /dev/null +++ b/CLI/RemoteCli.php @@ -0,0 +1,29 @@ +connection = $connection; + if ($cwd) { + $this->connection->exec('cd -P ' . escapeshellarg($cwd)); + } + } + + public function execute(string $command) + { + $this->connection->exec($command); + } + +} diff --git a/CacheClearer/CacheClearerInterface.php b/CacheClearer/CacheClearerInterface.php new file mode 100644 index 0000000..b871365 --- /dev/null +++ b/CacheClearer/CacheClearerInterface.php @@ -0,0 +1,10 @@ +has(CacheClearProvider::class)) { + return; + } + $definition = $container->getDefinition(CacheClearProvider::class); + + $taggedServices = $container->findTaggedServiceIds('domainator.cacheclearer'); + + foreach ($taggedServices as $id => $tags) { + foreach ($tags as $attributes) { + $definition->addMethodCall('registerCacheClearer', array(new Reference($id), $attributes['for'])); + } + } + } +} diff --git a/DependencyInjection/Compiler/CliFactoryProviderCompilerPass.php b/DependencyInjection/Compiler/CliFactoryProviderCompilerPass.php new file mode 100644 index 0000000..4fb94ef --- /dev/null +++ b/DependencyInjection/Compiler/CliFactoryProviderCompilerPass.php @@ -0,0 +1,33 @@ +has(CliFactoryProvider::class)) { + return; + } + $definition = $container->getDefinition(CliFactoryProvider::class); + + $taggedServices = $container->findTaggedServiceIds('domainator.clifactory'); + + foreach ($taggedServices as $id => $tags) { + foreach ($tags as $attributes) { + $definition->addMethodCall('registerCliFactory', array(new Reference($id), $attributes['for'])); + } + } + } +} diff --git a/Exception/NoCacheClearerFoundException.php b/Exception/NoCacheClearerFoundException.php new file mode 100644 index 0000000..fd82f82 --- /dev/null +++ b/Exception/NoCacheClearerFoundException.php @@ -0,0 +1,7 @@ +cacheClearers[$class] = $clearer; + } + + /** + * @param mixed $object + * + * @return CacheClearerInterface + * + * @throws NoCacheClearerFoundException + */ + public function getCacheClearerFor($object) + { + $class = get_class($object); + + if (!$class || !isset($this->cacheClearers[$class])) { + throw new NoCacheClearerFoundException('No cache clearer found for ' . $class); + } + + return $this->cacheClearers[$class]; + } +} diff --git a/Provider/CliFactoryProvider.php b/Provider/CliFactoryProvider.php new file mode 100644 index 0000000..99d7937 --- /dev/null +++ b/Provider/CliFactoryProvider.php @@ -0,0 +1,50 @@ +defaultCliFactory = $defaultCliFactory; + } + + public function registerCliFactory(CliFactoryInterface $cliFactory, $class) + { + $this->cliFactories[$class] = $cliFactory; + } + + /** + * @param mixed $object + * + * @return CliInterface + */ + public function createCliFor($object) + { + $class = get_class($object); + + if (!$class || !isset($this->cliFactories[$class])) { + if (!($this->defaultCliFactory instanceof CliFactoryInterface)) { + throw new NoCliFactoryFoundException('No cli factory found for ' . $class); + } + return $this->defaultCliFactory->create($object); + } + + return $this->cliFactories[$class]->create($object); + } +} diff --git a/Provisioner/CacheClearBuildProvisioner.php b/Provisioner/CacheClearBuildProvisioner.php new file mode 100644 index 0000000..fd71bc9 --- /dev/null +++ b/Provisioner/CacheClearBuildProvisioner.php @@ -0,0 +1,56 @@ +cliFactoryProvider = $cliFactoryProvider; + $this->cacheClearProvider = $cacheClearProvider; + } + + protected function doRun() + { + $appEnv = $this->task->getApplicationEnvironment(); + try { + $this->cacheClearProvider + ->getCacheClearerFor($appEnv->getApplication()) + ->clearCache( + $appEnv, + $this->cliFactoryProvider->createCliFor($appEnv) + ); + } + catch (NoCacheClearerFoundException $cacheEx) { + // There is no cache clearer registered for this application type, + // meaning we can't clear cache for it. This probably shouldn't make + // the task fail, but should we log it somehow? + } + catch (NoCliFactoryFoundException $cliEx) { + // There is no cli factory registered for this application + // envrironment, meaning we can't clear cache for it. This probably + // shouldn't make the task fail, but should we log it somehow? + } + } + + public function getName() + { + return 'Clear caches'; + } +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 590eb84..64b3b29 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -29,3 +29,15 @@ services: tags: [form.type] DigipolisGent\Domainator9k\CoreBundle\Twig\TemplateHelpExtension: tags: [twig.extension] + DigipolisGent\Domainator9k\CoreBundle\Provisioner\CacheClearBuildProvisioner: + tags: + - {name: domainator.provisioner.build, priority: -90} + arguments: + - '@DigipolisGent\Domainator9k\CoreBundle\Provider\CliFactoryProvider' + - '@DigipolisGent\Domainator9k\CoreBundle\Provider\CacheClearProvider' + DigipolisGent\Domainator9k\CoreBundle\CLI\DefaultCliFactory: + DigipolisGent\Domainator9k\CoreBundle\Provider\CacheClearProvider: + DigipolisGent\Domainator9k\CoreBundle\Provider\CliFactoryProvider: + arguments: + - '@DigipolisGent\Domainator9k\CoreBundle\CLI\DefaultCliFactory' + diff --git a/Tests/CLI/DefaultCliFactoryTest.php b/Tests/CLI/DefaultCliFactoryTest.php new file mode 100644 index 0000000..8f1ca0a --- /dev/null +++ b/Tests/CLI/DefaultCliFactoryTest.php @@ -0,0 +1,91 @@ +keyCreated = true; + } + } + + protected function tearDown() + { + parent::tearDown(); + if ($this->keyCreated) { + unlink(rtrim(Path::getHomeDirectory(), '/') . '/.ssh/id_rsa'); + } + } + + public function testCreate() + { + $factory = new DefaultCliFactory(); + $appEnv = $this->getMockBuilder(ApplicationEnvironment::class)->getMock(); + $env = $this->getMockBuilder(Environment::class)->getMock(); + $app = $this->getMockBuilder(AbstractApplication::class)->getMock(); + $appEnv->expects($this->once())->method('getEnvironment')->willReturn($env); + $appEnv->expects($this->once())->method('getApplication')->willReturn($app); + + $server = $this->getMockBuilder(VirtualServer::class)->getMock(); + $server->expects($this->once())->method('isTaskServer')->willreturn(false); + + $env->expects($this->once())->method('getVirtualServers')->willReturn(new ArrayCollection([$server])); + $this->assertNull($factory->create($appEnv)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage SSH login failed. + */ + public function testCreateLoginFailed() + { + // We have untestable code in DefaultCliFactory since the ssh client + // can't me mocked and it would be serious overkill to abstract it to + // yet another factory just so we can mock it here. So the best we can + // do is test up until the login and let it fail. + $factory = new DefaultCliFactory(); + $appEnv = $this->getMockBuilder(ApplicationEnvironment::class)->getMock(); + $env = $this->getMockBuilder(Environment::class)->getMock(); + $app = $this->getMockBuilder(AbstractApplication::class)->getMock(); + $appEnv->expects($this->once())->method('getEnvironment')->willReturn($env); + $appEnv->expects($this->once())->method('getApplication')->willReturn($app); + + $server = $this->getMockBuilder(VirtualServer::class)->getMock(); + $server->expects($this->once())->method('isTaskServer')->willreturn(true); + $server->expects($this->once())->method('getHost')->willreturn('localhost'); + $server->expects($this->once())->method('getPort')->willreturn(22); + + $env->expects($this->once())->method('getVirtualServers')->willReturn(new ArrayCollection([$server])); + $this->assertNull($factory->create($appEnv)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testCreateUnsuppoerted() + { + $factory = new DefaultCliFactory(); + $object = $this->getMockBuilder('\stdClass')->getMock(); + $factory->create($object); + } + +} diff --git a/Tests/CLI/RemoteCliTest.php b/Tests/CLI/RemoteCliTest.php new file mode 100644 index 0000000..660b1d5 --- /dev/null +++ b/Tests/CLI/RemoteCliTest.php @@ -0,0 +1,23 @@ +getMockBuilder(SSH2::class)->disableOriginalConstructor()->getMock(); + $dir = escapeshellarg('/some/dir'); + $connection->expects($this->at(0))->method('exec')->with('cd -P ' . $dir); + $connection->expects($this->at(1))->method('exec')->with('some command'); + $cli = new RemoteCli($connection, '/some/dir'); + $cli->execute('some command'); + } +} diff --git a/Tests/DependencyInjection/Compiler/CacheClearProviderCompilerPassTest.php b/Tests/DependencyInjection/Compiler/CacheClearProviderCompilerPassTest.php new file mode 100644 index 0000000..92fcfd2 --- /dev/null +++ b/Tests/DependencyInjection/Compiler/CacheClearProviderCompilerPassTest.php @@ -0,0 +1,49 @@ +getMockBuilder(ContainerBuilder::class)->disableOriginalConstructor()->getMock(); + $container->expects($this->once())->method('has')->with(CacheClearProvider::class)->willReturn(false); + $pass = new CacheClearProviderCompilerPass(); + $this->assertNull($pass->process($container)); + } + + public function testProvider() + { + $container = $this->getMockBuilder(ContainerBuilder::class)->disableOriginalConstructor()->getMock(); + $container->expects($this->once())->method('has')->with(CacheClearProvider::class)->willReturn(true); + + $id = 'my_service_id'; + + $container->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('domainator.cacheclearer') + ->willReturn([ + $id => [ + ['for' => '\stdClass'] + ] + ]); + + $definition = $this->getMockBuilder(Definition::class)->disableOriginalConstructor()->getMock(); + $definition->expects($this->once())->method('addMethodCall')->with( + 'registerCacheClearer', + $this->callback(function (array $args) use ($id) { + return ($args[0] instanceof Reference) && (string)$args[0] == $id && $args[1] == '\stdClass'; + }) + ); + + $container->expects($this->once())->method('getDefinition')->with(CacheClearProvider::class)->willReturn($definition); + $pass = new CacheClearProviderCompilerPass(); + $pass->process($container); + } +} diff --git a/Tests/DependencyInjection/Compiler/CliFactoryProviderCompilerPassTest.php b/Tests/DependencyInjection/Compiler/CliFactoryProviderCompilerPassTest.php new file mode 100644 index 0000000..cf40f4b --- /dev/null +++ b/Tests/DependencyInjection/Compiler/CliFactoryProviderCompilerPassTest.php @@ -0,0 +1,49 @@ +getMockBuilder(ContainerBuilder::class)->disableOriginalConstructor()->getMock(); + $container->expects($this->once())->method('has')->with(CliFactoryProvider::class)->willReturn(false); + $pass = new CliFactoryProviderCompilerPass(); + $this->assertNull($pass->process($container)); + } + + public function testProvider() + { + $container = $this->getMockBuilder(ContainerBuilder::class)->disableOriginalConstructor()->getMock(); + $container->expects($this->once())->method('has')->with(CliFactoryProvider::class)->willReturn(true); + + $id = 'my_service_id'; + + $container->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('domainator.clifactory') + ->willReturn([ + $id => [ + ['for' => '\stdClass'] + ] + ]); + + $definition = $this->getMockBuilder(Definition::class)->disableOriginalConstructor()->getMock(); + $definition->expects($this->once())->method('addMethodCall')->with( + 'registerCliFactory', + $this->callback(function (array $args) use ($id) { + return ($args[0] instanceof Reference) && (string)$args[0] == $id && $args[1] == '\stdClass'; + }) + ); + + $container->expects($this->once())->method('getDefinition')->with(CliFactoryProvider::class)->willReturn($definition); + $pass = new CliFactoryProviderCompilerPass(); + $pass->process($container); + } +} diff --git a/Tests/Provider/CacheClearProviderTest.php b/Tests/Provider/CacheClearProviderTest.php new file mode 100644 index 0000000..8e3b474 --- /dev/null +++ b/Tests/Provider/CacheClearProviderTest.php @@ -0,0 +1,35 @@ +getMockBuilder('\stdClass')->getMock(); + $class = get_class($object); + $clearer = $this->getMockBuilder(CacheClearerInterface::class)->getMock(); + $cacheClearProvider->registerCacheClearer($clearer, $class); + + $this->assertEquals($clearer, $cacheClearProvider->getCacheClearerFor($object)); + } + + /** + * @expectedException \DigipolisGent\Domainator9k\CoreBundle\Exception\NoCacheClearerFoundException + */ + public function testNoClearer() + { + $cacheClearProvider = new CacheClearProvider(); + $object = $this->getMockBuilder('\stdClass')->getMock(); + $class = get_class($object); + $cacheClearProvider->getCacheClearerFor($object); + } + +} diff --git a/Tests/Provider/CliFactoryProviderTest.php b/Tests/Provider/CliFactoryProviderTest.php new file mode 100644 index 0000000..92d0ee7 --- /dev/null +++ b/Tests/Provider/CliFactoryProviderTest.php @@ -0,0 +1,49 @@ +getMockBuilder('\stdClass')->getMock(); + $class = get_class($object); + $cli = $this->getMockBuilder(CliInterface::class)->getMock(); + $factory = $this->getMockBuilder(CliFactoryInterface::class)->getMock(); + $factory->expects($this->once())->method('create')->with($object)->willReturn($cli); + $cliFactoryProvider->registerCliFactory($factory, $class); + + $this->assertEquals($cli, $cliFactoryProvider->createCliFor($object)); + } + + /** + * @expectedException \DigipolisGent\Domainator9k\CoreBundle\Exception\NoCliFactoryFoundException + */ + public function testNoClearer() + { + $cacheClearProvider = new CliFactoryProvider(); + $object = $this->getMockBuilder('\stdClass')->getMock(); + $class = get_class($object); + $cacheClearProvider->createCliFor($object); + } + + public function testDefaultFactory() + { + $cli = $this->getMockBuilder(CliInterface::class)->getMock(); + $object = $this->getMockBuilder('\stdClass')->getMock(); + $factory = $this->getMockBuilder(CliFactoryInterface::class)->getMock(); + $factory->expects($this->once())->method('create')->with($object)->willReturn($cli); + $cliFactoryProvider = new CliFactoryProvider($factory); + + $this->assertEquals($cli, $cliFactoryProvider->createCliFor($object)); + } + +} diff --git a/Tests/Provisioner/CacheClearBuildProvisionerTest.php b/Tests/Provisioner/CacheClearBuildProvisionerTest.php new file mode 100644 index 0000000..734b2d8 --- /dev/null +++ b/Tests/Provisioner/CacheClearBuildProvisionerTest.php @@ -0,0 +1,55 @@ +assertEquals('Clear caches', $provisioner->getName()); + } + + public function testRun() + { + $cliFactoryProvider = new CliFactoryProvider(); + $cacheClearProvider = new CacheClearProvider(); + + $application = $this->getMockBuilder(AbstractApplication::class)->getMock(); + + $appEnv = $this->getMockBuilder(ApplicationEnvironment::class)->getMock(); + $appEnv->expects($this->once())->method('getApplication')->willReturn($application); + + $task = $this->getMockBuilder(Task::class)->getMock(); + $task->expects($this->once())->method('getApplicationEnvironment')->willReturn($appEnv); + + $cli = $this->getMockBuilder(CliInterface::class)->getMock(); + + $cliFactory = $this->getMockBuilder(CliFactoryInterface::class)->getMock(); + $cliFactory->expects($this->once())->method('create')->with($appEnv)->willReturn($cli); + + $cliFactoryProvider->registerCliFactory($cliFactory, get_class($appEnv)); + + $clearer = $this->getMockBuilder(CacheClearerInterface::class)->getMock(); + $clearer->expects($this->once())->method('clearCache')->with($appEnv, $cli); + + $cacheClearProvider->registerCacheClearer($clearer, get_class($application)); + + $provisioner = new CacheClearBuildProvisioner($cliFactoryProvider, $cacheClearProvider); + $provisioner->setTask($task); + $provisioner->run(); + } +} diff --git a/phpunit.xml b/phpunit.xml index d84bd06..e6dbf79 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,4 +1,11 @@ - + ./Tests From b4b56a0e535fd325a613e2b9a6392543c0928042 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 19 Jun 2018 11:11:35 +0200 Subject: [PATCH 086/104] WEBDOM-390: Code style fixes. --- CLI/DefaultCliFactory.php | 6 +++--- CLI/RemoteCli.php | 1 - .../Compiler/CacheClearProviderCompilerPass.php | 1 - .../Compiler/CliFactoryProviderCompilerPass.php | 2 -- Provider/CliFactoryProvider.php | 3 ++- Provisioner/CacheClearBuildProvisioner.php | 6 ++---- 6 files changed, 7 insertions(+), 12 deletions(-) diff --git a/CLI/DefaultCliFactory.php b/CLI/DefaultCliFactory.php index 370bb7b..0fc3643 100644 --- a/CLI/DefaultCliFactory.php +++ b/CLI/DefaultCliFactory.php @@ -22,11 +22,11 @@ public function create($object): ?CliInterface . ', ' . get_class($object) . ' given.' ); } - $applicationEnvironment = $object; + $appEnv = $object; /** @var Environment $environment */ - $environment = $applicationEnvironment->getEnvironment(); + $environment = $appEnv->getEnvironment(); /** @var AbstractApplication $application */ - $application = $applicationEnvironment->getApplication(); + $application = $appEnv->getApplication(); /** @var VirtualServer[] $servers */ $servers = $environment->getVirtualServers(); diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index 28d0faa..1bff5d3 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -25,5 +25,4 @@ public function execute(string $command) { $this->connection->exec($command); } - } diff --git a/DependencyInjection/Compiler/CacheClearProviderCompilerPass.php b/DependencyInjection/Compiler/CacheClearProviderCompilerPass.php index 13a163d..1ade2ea 100644 --- a/DependencyInjection/Compiler/CacheClearProviderCompilerPass.php +++ b/DependencyInjection/Compiler/CacheClearProviderCompilerPass.php @@ -1,6 +1,5 @@ cliFactoryProvider->createCliFor($appEnv) ); - } - catch (NoCacheClearerFoundException $cacheEx) { + } catch (NoCacheClearerFoundException $cacheEx) { // There is no cache clearer registered for this application type, // meaning we can't clear cache for it. This probably shouldn't make // the task fail, but should we log it somehow? - } - catch (NoCliFactoryFoundException $cliEx) { + } catch (NoCliFactoryFoundException $cliEx) { // There is no cli factory registered for this application // envrironment, meaning we can't clear cache for it. This probably // shouldn't make the task fail, but should we log it somehow? From 815e0f381f2339d00553e5481f755a3966516a1c Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 19 Jun 2018 11:15:52 +0200 Subject: [PATCH 087/104] Fix PHPUnit version constraint. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 07dabe3..c0dcc15 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "roave/better-reflection": "^3" }, "require-dev": { - "phpunit/phpunit": "6.5" + "phpunit/phpunit": "^6.5" }, "autoload": { "psr-4": { From 7dc524529006fa4940a85b5a50c02d6abf341b9e Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 19 Jun 2018 11:22:12 +0200 Subject: [PATCH 088/104] WEBDOM-390: Fix tests. --- Tests/CLI/DefaultCliFactoryTest.php | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/Tests/CLI/DefaultCliFactoryTest.php b/Tests/CLI/DefaultCliFactoryTest.php index 8f1ca0a..efc5abe 100644 --- a/Tests/CLI/DefaultCliFactoryTest.php +++ b/Tests/CLI/DefaultCliFactoryTest.php @@ -37,31 +37,10 @@ protected function tearDown() } public function testCreate() - { - $factory = new DefaultCliFactory(); - $appEnv = $this->getMockBuilder(ApplicationEnvironment::class)->getMock(); - $env = $this->getMockBuilder(Environment::class)->getMock(); - $app = $this->getMockBuilder(AbstractApplication::class)->getMock(); - $appEnv->expects($this->once())->method('getEnvironment')->willReturn($env); - $appEnv->expects($this->once())->method('getApplication')->willReturn($app); - - $server = $this->getMockBuilder(VirtualServer::class)->getMock(); - $server->expects($this->once())->method('isTaskServer')->willreturn(false); - - $env->expects($this->once())->method('getVirtualServers')->willReturn(new ArrayCollection([$server])); - $this->assertNull($factory->create($appEnv)); - } - - /** - * @expectedException \Exception - * @expectedExceptionMessage SSH login failed. - */ - public function testCreateLoginFailed() { // We have untestable code in DefaultCliFactory since the ssh client // can't me mocked and it would be serious overkill to abstract it to - // yet another factory just so we can mock it here. So the best we can - // do is test up until the login and let it fail. + // yet another factory just so we can mock it here. $factory = new DefaultCliFactory(); $appEnv = $this->getMockBuilder(ApplicationEnvironment::class)->getMock(); $env = $this->getMockBuilder(Environment::class)->getMock(); @@ -70,9 +49,7 @@ public function testCreateLoginFailed() $appEnv->expects($this->once())->method('getApplication')->willReturn($app); $server = $this->getMockBuilder(VirtualServer::class)->getMock(); - $server->expects($this->once())->method('isTaskServer')->willreturn(true); - $server->expects($this->once())->method('getHost')->willreturn('localhost'); - $server->expects($this->once())->method('getPort')->willreturn(22); + $server->expects($this->once())->method('isTaskServer')->willreturn(false); $env->expects($this->once())->method('getVirtualServers')->willReturn(new ArrayCollection([$server])); $this->assertNull($factory->create($appEnv)); From 8254313f7886ea131a1158ffa0b5831719231b39 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Tue, 19 Jun 2018 16:57:22 +0200 Subject: [PATCH 089/104] WEBDOM-390: Refactoring based on review comments. - Written better exception message when SSH login fails. - Used the execute method in the RemoteCli constructor. - Added docblock for CliInterface::execute(). - Introduced finer grained exception handling in CacheClearProvider. - Introduced finer grained exception handling in CliFactoryProvider. - Added logging to CacheClearBuildProvisioner. --- CLI/CliInterface.php | 6 ++++ CLI/DefaultCliFactory.php | 6 ++-- CLI/RemoteCli.php | 17 ++++++++- Provider/CacheClearProvider.php | 13 ++++++- Provider/CliFactoryProvider.php | 19 ++++++++-- Provisioner/CacheClearBuildProvisioner.php | 35 ++++++++++++++----- Resources/config/services.yml | 1 + .../CacheClearBuildProvisionerTest.php | 11 ++++-- 8 files changed, 91 insertions(+), 17 deletions(-) diff --git a/CLI/CliInterface.php b/CLI/CliInterface.php index 0407deb..e805f95 100644 --- a/CLI/CliInterface.php +++ b/CLI/CliInterface.php @@ -4,5 +4,11 @@ interface CliInterface { + /** + * Executes a command. + * + * @param string $command + * The (properly shell-escaped) command to execute. + */ public function execute(string $command); } diff --git a/CLI/DefaultCliFactory.php b/CLI/DefaultCliFactory.php index 0fc3643..fdb7abd 100644 --- a/CLI/DefaultCliFactory.php +++ b/CLI/DefaultCliFactory.php @@ -39,12 +39,14 @@ public function create($object): ?CliInterface $user = $application->getNameCanonical(); $keyLocation = rtrim(Path::getHomeDirectory(), '/') . '/.ssh/id_rsa'; - $ssh = new SSH2($server->getHost(), $server->getPort()); + $host = $server->getHost(); + $port = $server->getPort() ?: 22; + $ssh = new SSH2($host, $port); $key = new RSA(); $key->loadKey(file_get_contents($keyLocation)); if (!$ssh->login($user, $key)) { - throw new \Exception('SSH login failed.'); + throw new \Exception(sprintf('SSH login for %s@%s:%s failed.', $user, $host, $port)); } return new RemoteCli($ssh); } diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index 1bff5d3..4d2c689 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -13,14 +13,29 @@ class RemoteCli implements CliInterface protected $connection; + /** + * RemoteCli class constructor. + * + * @param SSH2 $connection + * The ssh connection to execute the commands on. + * + * @param string $cwd + * The current working directory to execute the commands from. + */ public function __construct(SSH2 $connection, $cwd = null) { $this->connection = $connection; if ($cwd) { - $this->connection->exec('cd -P ' . escapeshellarg($cwd)); + $this->execute('cd -P ' . escapeshellarg($cwd)); } } + /** + * Executes a command. + * + * @param string $command + * The (properly shell-escaped) command to execute. + */ public function execute(string $command) { $this->connection->exec($command); diff --git a/Provider/CacheClearProvider.php b/Provider/CacheClearProvider.php index 0aed0a8..6dc2c29 100644 --- a/Provider/CacheClearProvider.php +++ b/Provider/CacheClearProvider.php @@ -19,13 +19,24 @@ public function registerCacheClearer(CacheClearerInterface $clearer, $class) * * @return CacheClearerInterface * + * @throws \InvalidArgumentException * @throws NoCacheClearerFoundException */ public function getCacheClearerFor($object) { + if (!is_object($object)) { + throw new \InvalidArgumentException( + sprintf( + '%s::getCacheClearerFor() expects parameter 1 to be an object, %s given.', + get_called_class(), + gettype($object) + ) + ); + } + $class = get_class($object); - if (!$class || !isset($this->cacheClearers[$class])) { + if (!isset($this->cacheClearers[$class])) { throw new NoCacheClearerFoundException('No cache clearer found for ' . $class); } diff --git a/Provider/CliFactoryProvider.php b/Provider/CliFactoryProvider.php index b0e0cc8..f6c87b5 100644 --- a/Provider/CliFactoryProvider.php +++ b/Provider/CliFactoryProvider.php @@ -34,14 +34,29 @@ public function registerCliFactory(CliFactoryInterface $cliFactory, $class) * @param mixed $object * * @return CliInterface + * + * @throws \InvalidArgumentException + * @throws NoCliFactoryFoundException */ public function createCliFor($object) { + if (!is_object($object)) { + throw new \InvalidArgumentException( + sprintf( + '%s::createCliFor() expects parameter 1 to be an object, %s given.', + get_called_class(), + gettype($object) + ) + ); + } + $class = get_class($object); - if (!$class || !isset($this->cliFactories[$class])) { + if (!isset($this->cliFactories[$class])) { if (!($this->defaultCliFactory instanceof CliFactoryInterface)) { - throw new NoCliFactoryFoundException('No cli factory found for ' . $class); + throw new NoCliFactoryFoundException( + sprintf('No cli factory found for %s and no default factory given.', $class) + ); } return $this->defaultCliFactory->create($object); } diff --git a/Provisioner/CacheClearBuildProvisioner.php b/Provisioner/CacheClearBuildProvisioner.php index 1eb5d0c..2d2e9d5 100644 --- a/Provisioner/CacheClearBuildProvisioner.php +++ b/Provisioner/CacheClearBuildProvisioner.php @@ -6,6 +6,7 @@ use DigipolisGent\Domainator9k\CoreBundle\Exception\NoCliFactoryFoundException; use DigipolisGent\Domainator9k\CoreBundle\Provider\CacheClearProvider; use DigipolisGent\Domainator9k\CoreBundle\Provider\CliFactoryProvider; +use DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService; class CacheClearBuildProvisioner extends AbstractProvisioner { @@ -20,30 +21,46 @@ class CacheClearBuildProvisioner extends AbstractProvisioner */ protected $cacheClearProvider; - public function __construct(CliFactoryProvider $cliFactoryProvider, CacheClearProvider $cacheClearProvider) - { + /** + * @var TaskLoggerService + */ + protected $taskLoggerService; + + public function __construct( + CliFactoryProvider $cliFactoryProvider, + CacheClearProvider $cacheClearProvider, + TaskLoggerService $taskLoggerService + ) { $this->cliFactoryProvider = $cliFactoryProvider; $this->cacheClearProvider = $cacheClearProvider; + $this->taskLoggerService = $taskLoggerService; } protected function doRun() { $appEnv = $this->task->getApplicationEnvironment(); + $application = $appEnv->getApplication(); + $environment = $appEnv->getEnvironment(); + + $this->taskLoggerService->addLogHeader( + $this->task, + sprintf( + 'Clearing cache for %s on %s.', + $application->getName(), + $environment->getName() + ) + ); try { $this->cacheClearProvider - ->getCacheClearerFor($appEnv->getApplication()) + ->getCacheClearerFor($application) ->clearCache( $appEnv, $this->cliFactoryProvider->createCliFor($appEnv) ); } catch (NoCacheClearerFoundException $cacheEx) { - // There is no cache clearer registered for this application type, - // meaning we can't clear cache for it. This probably shouldn't make - // the task fail, but should we log it somehow? + $this->taskLoggerService->addWarningLogMessage($this->task, $cacheEx->getMessage(), 2); } catch (NoCliFactoryFoundException $cliEx) { - // There is no cli factory registered for this application - // envrironment, meaning we can't clear cache for it. This probably - // shouldn't make the task fail, but should we log it somehow? + $this->taskLoggerService->addWarningLogMessage($this->task, $cliEx->getMessage(), 2); } } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 64b3b29..a0f443f 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -35,6 +35,7 @@ services: arguments: - '@DigipolisGent\Domainator9k\CoreBundle\Provider\CliFactoryProvider' - '@DigipolisGent\Domainator9k\CoreBundle\Provider\CacheClearProvider' + - '@DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService' DigipolisGent\Domainator9k\CoreBundle\CLI\DefaultCliFactory: DigipolisGent\Domainator9k\CoreBundle\Provider\CacheClearProvider: DigipolisGent\Domainator9k\CoreBundle\Provider\CliFactoryProvider: diff --git a/Tests/Provisioner/CacheClearBuildProvisionerTest.php b/Tests/Provisioner/CacheClearBuildProvisionerTest.php index 734b2d8..e0d3b94 100644 --- a/Tests/Provisioner/CacheClearBuildProvisionerTest.php +++ b/Tests/Provisioner/CacheClearBuildProvisionerTest.php @@ -7,10 +7,12 @@ use DigipolisGent\Domainator9k\CoreBundle\CLI\CliInterface; use DigipolisGent\Domainator9k\CoreBundle\Entity\AbstractApplication; use DigipolisGent\Domainator9k\CoreBundle\Entity\ApplicationEnvironment; +use DigipolisGent\Domainator9k\CoreBundle\Entity\Environment; use DigipolisGent\Domainator9k\CoreBundle\Entity\Task; use DigipolisGent\Domainator9k\CoreBundle\Provider\CacheClearProvider; use DigipolisGent\Domainator9k\CoreBundle\Provider\CliFactoryProvider; use DigipolisGent\Domainator9k\CoreBundle\Provisioner\CacheClearBuildProvisioner; +use DigipolisGent\Domainator9k\CoreBundle\Service\TaskLoggerService; use PHPUnit\Framework\TestCase; class CacheClearBuildProvisionerTest extends TestCase @@ -19,7 +21,8 @@ public function testGetName() { $cliFactoryProvider = new CliFactoryProvider(); $cacheClearProvider = new CacheClearProvider(); - $provisioner = new CacheClearBuildProvisioner($cliFactoryProvider, $cacheClearProvider); + $taskLoggerService = $this->getMockBuilder(TaskLoggerService::class)->disableOriginalConstructor()->getMock(); + $provisioner = new CacheClearBuildProvisioner($cliFactoryProvider, $cacheClearProvider, $taskLoggerService); $this->assertEquals('Clear caches', $provisioner->getName()); } @@ -29,9 +32,11 @@ public function testRun() $cacheClearProvider = new CacheClearProvider(); $application = $this->getMockBuilder(AbstractApplication::class)->getMock(); + $environment = $this->getMockBuilder(Environment::class)->getMock(); $appEnv = $this->getMockBuilder(ApplicationEnvironment::class)->getMock(); $appEnv->expects($this->once())->method('getApplication')->willReturn($application); + $appEnv->expects($this->once())->method('getEnvironment')->willReturn($environment); $task = $this->getMockBuilder(Task::class)->getMock(); $task->expects($this->once())->method('getApplicationEnvironment')->willReturn($appEnv); @@ -48,7 +53,9 @@ public function testRun() $cacheClearProvider->registerCacheClearer($clearer, get_class($application)); - $provisioner = new CacheClearBuildProvisioner($cliFactoryProvider, $cacheClearProvider); + $taskLoggerService = $this->getMockBuilder(TaskLoggerService::class)->disableOriginalConstructor()->getMock(); + + $provisioner = new CacheClearBuildProvisioner($cliFactoryProvider, $cacheClearProvider, $taskLoggerService); $provisioner->setTask($task); $provisioner->run(); } From c07eca16dc954aa8f10775a3fe4a827fd0158c0e Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Wed, 20 Jun 2018 09:30:51 +0200 Subject: [PATCH 090/104] WEBDOM-390: Improved error handling and logging. --- CLI/CliInterface.php | 10 +++++++ CLI/RemoteCli.php | 24 ++++++++++++++++- CacheClearer/CacheClearerInterface.php | 9 +++++++ Provisioner/CacheClearBuildProvisioner.php | 9 +++++-- Tests/CLI/RemoteCliTest.php | 26 +++++++++++++++---- .../CacheClearBuildProvisionerTest.php | 2 +- 6 files changed, 71 insertions(+), 9 deletions(-) diff --git a/CLI/CliInterface.php b/CLI/CliInterface.php index e805f95..c61b0fe 100644 --- a/CLI/CliInterface.php +++ b/CLI/CliInterface.php @@ -9,6 +9,16 @@ interface CliInterface * * @param string $command * The (properly shell-escaped) command to execute. + * + * @return bool + * True on success, false on failure. */ public function execute(string $command); + + /** + * Get the output of the last execution. + * + * @return string + */ + public function getLastOutput(); } diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index 4d2c689..5cebc74 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -12,6 +12,11 @@ class RemoteCli implements CliInterface */ protected $connection; + /** + * @var string + */ + protected $lastOutput; + /** * RemoteCli class constructor. @@ -35,9 +40,26 @@ public function __construct(SSH2 $connection, $cwd = null) * * @param string $command * The (properly shell-escaped) command to execute. + * + * @return bool + * True on success, false on failure. */ public function execute(string $command) { - $this->connection->exec($command); + $result = $this->connection->exec($command); + $this->lastOutput = $result ? $result : ''; + + return $this->connection->getExitStatus() === 0; + } + + /** + * Get the output of the last execution. + * + * @return string + */ + public function getLastOutput() + { + return $this->lastOutput; } + } diff --git a/CacheClearer/CacheClearerInterface.php b/CacheClearer/CacheClearerInterface.php index b871365..6a5b125 100644 --- a/CacheClearer/CacheClearerInterface.php +++ b/CacheClearer/CacheClearerInterface.php @@ -6,5 +6,14 @@ interface CacheClearerInterface { + /** + * @param mixed $object + * The object to clear the cache for. + * @param CliInterface $cli + * The cli to execute the cache clear command on. + * + * @return bool + * True on success, false on failure. + */ public function clearCache($object, CliInterface $cli); } diff --git a/Provisioner/CacheClearBuildProvisioner.php b/Provisioner/CacheClearBuildProvisioner.php index 2d2e9d5..2a29d8c 100644 --- a/Provisioner/CacheClearBuildProvisioner.php +++ b/Provisioner/CacheClearBuildProvisioner.php @@ -51,12 +51,17 @@ protected function doRun() ) ); try { - $this->cacheClearProvider + $cli = $this->cliFactoryProvider->createCliFor($appEnv); + $result = $this->cacheClearProvider ->getCacheClearerFor($application) ->clearCache( $appEnv, - $this->cliFactoryProvider->createCliFor($appEnv) + $cli ); + if (!$result) { + $this->taskLoggerService->addErrorLogMessage($this->task, 'Cache clear failed.', 2); + throw new \Exception($cli->getLastOutput()); + } } catch (NoCacheClearerFoundException $cacheEx) { $this->taskLoggerService->addWarningLogMessage($this->task, $cacheEx->getMessage(), 2); } catch (NoCliFactoryFoundException $cliEx) { diff --git a/Tests/CLI/RemoteCliTest.php b/Tests/CLI/RemoteCliTest.php index 660b1d5..09086c9 100644 --- a/Tests/CLI/RemoteCliTest.php +++ b/Tests/CLI/RemoteCliTest.php @@ -9,15 +9,31 @@ class RemoteCliTest extends TestCase { - - public function testExecute() { $connection = $this->getMockBuilder(SSH2::class)->disableOriginalConstructor()->getMock(); $dir = escapeshellarg('/some/dir'); - $connection->expects($this->at(0))->method('exec')->with('cd -P ' . $dir); - $connection->expects($this->at(1))->method('exec')->with('some command'); + $command = 'some command'; + $output = 'some output'; + $connection + ->expects($this->at(0)) + ->method('exec') + ->with('cd -P ' . $dir); + $connection + ->expects($this->at(1)) + ->method('getExitStatus') + ->willReturn(0); + $connection + ->expects($this->at(2)) + ->method('exec') + ->with($command) + ->willReturn($output); + $connection + ->expects($this->at(3)) + ->method('getExitStatus') + ->willReturn(0); $cli = new RemoteCli($connection, '/some/dir'); - $cli->execute('some command'); + $this->assertEquals(true, $cli->execute($command)); + $this->assertEquals($output, $cli->getLastOutput()); } } diff --git a/Tests/Provisioner/CacheClearBuildProvisionerTest.php b/Tests/Provisioner/CacheClearBuildProvisionerTest.php index e0d3b94..649e9cd 100644 --- a/Tests/Provisioner/CacheClearBuildProvisionerTest.php +++ b/Tests/Provisioner/CacheClearBuildProvisionerTest.php @@ -49,7 +49,7 @@ public function testRun() $cliFactoryProvider->registerCliFactory($cliFactory, get_class($appEnv)); $clearer = $this->getMockBuilder(CacheClearerInterface::class)->getMock(); - $clearer->expects($this->once())->method('clearCache')->with($appEnv, $cli); + $clearer->expects($this->once())->method('clearCache')->with($appEnv, $cli)->willReturn(true); $cacheClearProvider->registerCacheClearer($clearer, get_class($application)); From 32e5471fdccc6dc14296300f0436794be73530ff Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Wed, 20 Jun 2018 09:49:58 +0200 Subject: [PATCH 091/104] WEBDOM-390: Code style fix. --- CLI/RemoteCli.php | 1 - 1 file changed, 1 deletion(-) diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index 5cebc74..0ac539f 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -61,5 +61,4 @@ public function getLastOutput() { return $this->lastOutput; } - } From eee013240fc3053781af339cf29dcd5e7960643b Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Wed, 20 Jun 2018 14:24:34 +0200 Subject: [PATCH 092/104] WEBDOM-390: Added docblock per @daften's request. --- CLI/DefaultCliFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CLI/DefaultCliFactory.php b/CLI/DefaultCliFactory.php index fdb7abd..159731a 100644 --- a/CLI/DefaultCliFactory.php +++ b/CLI/DefaultCliFactory.php @@ -14,6 +14,10 @@ class DefaultCliFactory implements CliFactoryInterface { + /** + * It's a CliFactory, the method is called 'create', the return type is type + * hinted... What do you think it does!? + */ public function create($object): ?CliInterface { if (!($object instanceof ApplicationEnvironment)) { From b65024b30c0c57d51c18857d121d0a9668a0eba3 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Wed, 20 Jun 2018 14:26:00 +0200 Subject: [PATCH 093/104] WEBDOM-390: Fixed review remarks. - Added docblocks. - Removed unnecessary whiteline. --- CLI/CliFactoryInterface.php | 9 +++++++++ CLI/DefaultCliFactory.php | 3 +-- CLI/RemoteCli.php | 1 - 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CLI/CliFactoryInterface.php b/CLI/CliFactoryInterface.php index 2a2b4fb..54013ec 100644 --- a/CLI/CliFactoryInterface.php +++ b/CLI/CliFactoryInterface.php @@ -4,5 +4,14 @@ interface CliFactoryInterface { + /** + * Creates a CLI instance for a given object. + * + * @param mixed $object + * The object to get the CLI instance for. + * + * @return CliInterface + * The CLI for the given object. + */ public function create($object): ?CliInterface; } diff --git a/CLI/DefaultCliFactory.php b/CLI/DefaultCliFactory.php index 159731a..2578326 100644 --- a/CLI/DefaultCliFactory.php +++ b/CLI/DefaultCliFactory.php @@ -15,8 +15,7 @@ class DefaultCliFactory implements CliFactoryInterface { /** - * It's a CliFactory, the method is called 'create', the return type is type - * hinted... What do you think it does!? + * {@inheritdoc} */ public function create($object): ?CliInterface { diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index 0ac539f..ac5b87d 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -17,7 +17,6 @@ class RemoteCli implements CliInterface */ protected $lastOutput; - /** * RemoteCli class constructor. * From 511936d607b3ca75b2f8459eb1cb9832545f3741 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 22 Jun 2018 11:43:40 +0200 Subject: [PATCH 094/104] WEBDOM-390: Use the command builder to execute commands. --- CLI/CliInterface.php | 8 +++++--- CLI/RemoteCli.php | 9 +++++---- composer.json | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CLI/CliInterface.php b/CLI/CliInterface.php index c61b0fe..cfff837 100644 --- a/CLI/CliInterface.php +++ b/CLI/CliInterface.php @@ -2,18 +2,20 @@ namespace DigipolisGent\Domainator9k\CoreBundle\CLI; +use DigipolisGent\CommandBuilder\CommandBuilder; + interface CliInterface { /** * Executes a command. * - * @param string $command - * The (properly shell-escaped) command to execute. + * @param CommandBuilder $command + * The command to execute. * * @return bool * True on success, false on failure. */ - public function execute(string $command); + public function execute(CommandBuilder $command); /** * Get the output of the last execution. diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index ac5b87d..97a2e06 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -2,6 +2,7 @@ namespace DigipolisGent\Domainator9k\CoreBundle\CLI; +use DigipolisGent\CommandBuilder\CommandBuilder; use phpseclib\Net\SSH2; class RemoteCli implements CliInterface @@ -30,20 +31,20 @@ public function __construct(SSH2 $connection, $cwd = null) { $this->connection = $connection; if ($cwd) { - $this->execute('cd -P ' . escapeshellarg($cwd)); + $this->execute(CommandBuilder::create('cd')->addFlag('P')->addArgument($cwd)); } } /** * Executes a command. * - * @param string $command - * The (properly shell-escaped) command to execute. + * @param CommandBuilder $command + * The command to execute. * * @return bool * True on success, false on failure. */ - public function execute(string $command) + public function execute(CommandBuilder $command) { $result = $this->connection->exec($command); $this->lastOutput = $result ? $result : ''; diff --git a/composer.json b/composer.json index c0dcc15..8aaa355 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "php": ">=7.1", "symfony/symfony": ">=3.4", "digipolisgent/setting-bundle": "dev-develop", + "digipolisgent/command-builder": "^1.0", "doctrine/doctrine-bundle": "^1.6", "doctrine/orm": "^2.5", "symfony/swiftmailer-bundle": "^2.3", From 08d16447dbd34241169e91926a44ac01091254c7 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 22 Jun 2018 11:53:27 +0200 Subject: [PATCH 095/104] WEBDOM-390: Fix tests. --- CLI/RemoteCli.php | 2 +- Tests/CLI/RemoteCliTest.php | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index 97a2e06..853d2ea 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -46,7 +46,7 @@ public function __construct(SSH2 $connection, $cwd = null) */ public function execute(CommandBuilder $command) { - $result = $this->connection->exec($command); + $result = $this->connection->exec($command->getCommand()); $this->lastOutput = $result ? $result : ''; return $this->connection->getExitStatus() === 0; diff --git a/Tests/CLI/RemoteCliTest.php b/Tests/CLI/RemoteCliTest.php index 09086c9..fe8a6bb 100644 --- a/Tests/CLI/RemoteCliTest.php +++ b/Tests/CLI/RemoteCliTest.php @@ -3,6 +3,7 @@ namespace DigipolisGent\Domainator9k\CoreBundle\Tests\CLI; +use DigipolisGent\CommandBuilder\CommandBuilder; use DigipolisGent\Domainator9k\CoreBundle\CLI\RemoteCli; use phpseclib\Net\SSH2; use PHPUnit\Framework\TestCase; @@ -12,13 +13,13 @@ class RemoteCliTest extends TestCase public function testExecute() { $connection = $this->getMockBuilder(SSH2::class)->disableOriginalConstructor()->getMock(); - $dir = escapeshellarg('/some/dir'); - $command = 'some command'; + $dir = '/some/dir'; + $execute = 'some command'; $output = 'some output'; $connection ->expects($this->at(0)) ->method('exec') - ->with('cd -P ' . $dir); + ->with(CommandBuilder::create('cd')->addFlag('P')->addArgument($dir)->getCommand()); $connection ->expects($this->at(1)) ->method('getExitStatus') @@ -26,14 +27,14 @@ public function testExecute() $connection ->expects($this->at(2)) ->method('exec') - ->with($command) + ->with(CommandBuilder::create($execute)->getCommand()) ->willReturn($output); $connection ->expects($this->at(3)) ->method('getExitStatus') ->willReturn(0); - $cli = new RemoteCli($connection, '/some/dir'); - $this->assertEquals(true, $cli->execute($command)); + $cli = new RemoteCli($connection, $dir); + $this->assertEquals(true, $cli->execute(CommandBuilder::create($execute))); $this->assertEquals($output, $cli->getLastOutput()); } } From 7565e0ef53d0558cab434e96c29875fe77ce1d3d Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 22 Jun 2018 14:57:40 +0200 Subject: [PATCH 096/104] Log cli output when clearing cache. --- Provisioner/CacheClearBuildProvisioner.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Provisioner/CacheClearBuildProvisioner.php b/Provisioner/CacheClearBuildProvisioner.php index 2a29d8c..bc6b936 100644 --- a/Provisioner/CacheClearBuildProvisioner.php +++ b/Provisioner/CacheClearBuildProvisioner.php @@ -62,6 +62,10 @@ protected function doRun() $this->taskLoggerService->addErrorLogMessage($this->task, 'Cache clear failed.', 2); throw new \Exception($cli->getLastOutput()); } + $output = $cli->getLastOutput(); + if ($output) { + $this->taskLoggerService->addInfoLogMessage($this->task, $output, 2); + } } catch (NoCacheClearerFoundException $cacheEx) { $this->taskLoggerService->addWarningLogMessage($this->task, $cacheEx->getMessage(), 2); } catch (NoCliFactoryFoundException $cliEx) { From ab81a962e788186d834b12cf04846aa928377c8a Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 25 Jun 2018 09:47:24 +0200 Subject: [PATCH 097/104] Added compiler passes, fixed ssh command cwd. --- CLI/RemoteCli.php | 18 ++++++++++++++---- DigipolisGentDomainator9kCoreBundle.php | 11 ++++++++--- Provider/CliFactoryProvider.php | 21 ++++++++++++--------- Tests/CLI/RemoteCliTest.php | 15 ++++----------- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index 853d2ea..be58037 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -18,6 +18,11 @@ class RemoteCli implements CliInterface */ protected $lastOutput; + /** + * @var string + */ + protected $cwd; + /** * RemoteCli class constructor. * @@ -30,9 +35,7 @@ class RemoteCli implements CliInterface public function __construct(SSH2 $connection, $cwd = null) { $this->connection = $connection; - if ($cwd) { - $this->execute(CommandBuilder::create('cd')->addFlag('P')->addArgument($cwd)); - } + $this->cwd = $cwd; } /** @@ -46,8 +49,15 @@ public function __construct(SSH2 $connection, $cwd = null) */ public function execute(CommandBuilder $command) { + if ($this->cwd) { + $command = CommandBuilder::create('cd') + ->addFlag('P') + ->addArgument($this->cwd) + ->onSuccess($command); + } + $this->lastOutput = 'Executing ' . $command . "\n"; $result = $this->connection->exec($command->getCommand()); - $this->lastOutput = $result ? $result : ''; + $this->lastOutput .= $result ? $result : ''; return $this->connection->getExitStatus() === 0; } diff --git a/DigipolisGentDomainator9kCoreBundle.php b/DigipolisGentDomainator9kCoreBundle.php index dbe24a6..386323c 100644 --- a/DigipolisGentDomainator9kCoreBundle.php +++ b/DigipolisGentDomainator9kCoreBundle.php @@ -2,12 +2,17 @@ namespace DigipolisGent\Domainator9k\CoreBundle; -use DigipolisGent\Domainator9k\CoreBundle\DependencyInjection\Compiler\ApplicationTypePass; -use DigipolisGent\Domainator9k\CoreBundle\DependencyInjection\Compiler\CiTypePass; -use DigipolisGent\Domainator9k\CoreBundle\DependencyInjection\Compiler\TaskPass; +use DigipolisGent\Domainator9k\CoreBundle\DependencyInjection\Compiler\CacheClearProviderCompilerPass; +use DigipolisGent\Domainator9k\CoreBundle\DependencyInjection\Compiler\CliFactoryProviderCompilerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class DigipolisGentDomainator9kCoreBundle extends Bundle { + public function build(ContainerBuilder $container) + { + parent::build($container); + $container->addCompilerPass(new CacheClearProviderCompilerPass()); + $container->addCompilerPass(new CliFactoryProviderCompilerPass()); + } } diff --git a/Provider/CliFactoryProvider.php b/Provider/CliFactoryProvider.php index f6c87b5..2eea257 100644 --- a/Provider/CliFactoryProvider.php +++ b/Provider/CliFactoryProvider.php @@ -5,6 +5,7 @@ use DigipolisGent\Domainator9k\CoreBundle\CLI\CliFactoryInterface; use DigipolisGent\Domainator9k\CoreBundle\CLI\CliInterface; use DigipolisGent\Domainator9k\CoreBundle\Exception\NoCliFactoryFoundException; +use Doctrine\ORM\Proxy\Proxy; class CliFactoryProvider { @@ -52,15 +53,17 @@ public function createCliFor($object) $class = get_class($object); - if (!isset($this->cliFactories[$class])) { - if (!($this->defaultCliFactory instanceof CliFactoryInterface)) { - throw new NoCliFactoryFoundException( - sprintf('No cli factory found for %s and no default factory given.', $class) - ); - } - return $this->defaultCliFactory->create($object); + if (!isset($this->cliFactories[$class]) && $object instanceof Proxy) { + $class = get_parent_class($object); } - - return $this->cliFactories[$class]->create($object); + if (isset($this->cliFactories[$class])) { + return $this->cliFactories[$class]->create($object); + } + if (!($this->defaultCliFactory instanceof CliFactoryInterface)) { + throw new NoCliFactoryFoundException( + sprintf('No cli factory found for %s and no default factory given.', $class) + ); + } + return $this->defaultCliFactory->create($object); } } diff --git a/Tests/CLI/RemoteCliTest.php b/Tests/CLI/RemoteCliTest.php index fe8a6bb..8aff8d7 100644 --- a/Tests/CLI/RemoteCliTest.php +++ b/Tests/CLI/RemoteCliTest.php @@ -16,25 +16,18 @@ public function testExecute() $dir = '/some/dir'; $execute = 'some command'; $output = 'some output'; + $command = CommandBuilder::create('cd')->addFlag('P')->addArgument($dir)->onSuccess($execute)->getCommand(); $connection ->expects($this->at(0)) ->method('exec') - ->with(CommandBuilder::create('cd')->addFlag('P')->addArgument($dir)->getCommand()); - $connection - ->expects($this->at(1)) - ->method('getExitStatus') - ->willReturn(0); - $connection - ->expects($this->at(2)) - ->method('exec') - ->with(CommandBuilder::create($execute)->getCommand()) + ->with($command) ->willReturn($output); $connection - ->expects($this->at(3)) + ->expects($this->at(1)) ->method('getExitStatus') ->willReturn(0); $cli = new RemoteCli($connection, $dir); $this->assertEquals(true, $cli->execute(CommandBuilder::create($execute))); - $this->assertEquals($output, $cli->getLastOutput()); + $this->assertEquals("Executing $command\n$output", $cli->getLastOutput()); } } From a26a2f30f278c81f02282bc7e162300fdc4d3b75 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Mon, 25 Jun 2018 09:55:17 +0200 Subject: [PATCH 098/104] Fix for doctrine proxies. --- Provider/CacheClearProvider.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Provider/CacheClearProvider.php b/Provider/CacheClearProvider.php index 6dc2c29..51bfcdf 100644 --- a/Provider/CacheClearProvider.php +++ b/Provider/CacheClearProvider.php @@ -4,6 +4,7 @@ use DigipolisGent\Domainator9k\CoreBundle\CacheClearer\CacheClearerInterface; use DigipolisGent\Domainator9k\CoreBundle\Exception\NoCacheClearerFoundException; +use Doctrine\ORM\Proxy\Proxy; class CacheClearProvider { @@ -35,11 +36,13 @@ public function getCacheClearerFor($object) } $class = get_class($object); - - if (!isset($this->cacheClearers[$class])) { - throw new NoCacheClearerFoundException('No cache clearer found for ' . $class); + if (!isset($this->cacheClearers[$class]) && $object instanceof Proxy) { + $class = get_parent_class($object); + } + if (isset($this->cacheClearers[$class])) { + return $this->cacheClearers[$class]; } - return $this->cacheClearers[$class]; + throw new NoCacheClearerFoundException('No cache clearer found for ' . $class); } } From 8c14275d6328b45091e36363540b673027c3b72a Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 6 Jul 2018 16:53:30 +0200 Subject: [PATCH 099/104] WEBDOM-379: Limit the allowed characters for an application name. --- Entity/AbstractApplication.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Entity/AbstractApplication.php b/Entity/AbstractApplication.php index d262872..0f6b6e2 100644 --- a/Entity/AbstractApplication.php +++ b/Entity/AbstractApplication.php @@ -36,6 +36,10 @@ abstract class AbstractApplication implements TemplateInterface * @ORM\Column(name="name", type="string", nullable=false) * @Assert\NotBlank() * @Assert\Length(min="2", max="255") + * @Assert\Regex( + * pattern="/^[a-z0-9\-]+$/", + * message="Name can only contain alphanumeric characters and dashes." + * ) */ protected $name; From dedc13a59bd5357a6ab5b7e6bec35c8f0036d803 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Fri, 13 Jul 2018 10:15:30 +0200 Subject: [PATCH 100/104] Added getconnection method. --- CLI/CliInterface.php | 2 ++ CLI/RemoteCli.php | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/CLI/CliInterface.php b/CLI/CliInterface.php index cfff837..b1a1005 100644 --- a/CLI/CliInterface.php +++ b/CLI/CliInterface.php @@ -23,4 +23,6 @@ public function execute(CommandBuilder $command); * @return string */ public function getLastOutput(); + + public function getConnection(); } diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php index be58037..3fa6144 100644 --- a/CLI/RemoteCli.php +++ b/CLI/RemoteCli.php @@ -71,4 +71,8 @@ public function getLastOutput() { return $this->lastOutput; } + + public function getConnection() { + return $this->connection; + } } From d5a37004667a5b62a769a3a36ea4d9a125350f66 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 27 Jul 2018 15:02:54 +0200 Subject: [PATCH 101/104] WEBDOM-398: Changed cache clear so failures don't throw an exception. --- Provisioner/CacheClearBuildProvisioner.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Provisioner/CacheClearBuildProvisioner.php b/Provisioner/CacheClearBuildProvisioner.php index bc6b936..22cac0c 100644 --- a/Provisioner/CacheClearBuildProvisioner.php +++ b/Provisioner/CacheClearBuildProvisioner.php @@ -60,7 +60,6 @@ protected function doRun() ); if (!$result) { $this->taskLoggerService->addErrorLogMessage($this->task, 'Cache clear failed.', 2); - throw new \Exception($cli->getLastOutput()); } $output = $cli->getLastOutput(); if ($output) { From b6ac43c77c51bcab344afa160b9552677c38c572 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 27 Jul 2018 16:16:34 +0200 Subject: [PATCH 102/104] WEBDOM-395: Added support for optional provisioners that won't run by default. --- Form/Type/TaskFormType.php | 21 +++++++++++++++++---- Provisioner/AbstractProvisioner.php | 5 +++++ Provisioner/CacheClearBuildProvisioner.php | 5 +++++ Provisioner/ProvisionerInterface.php | 1 + 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Form/Type/TaskFormType.php b/Form/Type/TaskFormType.php index 54d6e87..85e6bc7 100644 --- a/Form/Type/TaskFormType.php +++ b/Form/Type/TaskFormType.php @@ -44,8 +44,7 @@ public function __construct(FormService $formService, TaskRunnerService $taskRun public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); - $choices = []; - $provisioners = []; + switch ($options['type']) { case Task::TYPE_BUILD: $provisioners = $this->taskRunnerService->getBuildProvisioners(); @@ -54,17 +53,31 @@ public function buildForm(FormBuilderInterface $builder, array $options) case Task::TYPE_DESTROY: $provisioners = $this->taskRunnerService->getDestroyProvisioners(); break; + + default: + $provisioners = []; + break; } + $choices = []; + $defaults = []; foreach ($provisioners as $provisioner) { - $choices[$provisioner->getName()] = get_class($provisioner); + $class = get_class($provisioner); + $choices[$provisioner->getName()] = $class; + + if ($provisioner->isExecutedByDefault()) { + $defaults[] = $class; + } } + $builder->add('provisioners', ChoiceType::class, [ 'expanded' => true, 'multiple' => true, 'required' => false, 'choices' => $choices, - 'label' => 'Limit to following provisioners (selecting none will run all provisioners)', + 'data' => $defaults, + 'empty_data' => $defaults, + 'label' => 'Limit to following provisioners (selecting none will run the default provisioners)', ]); $builder->addEventSubscriber(new SettingFormListener($this->formService)); } diff --git a/Provisioner/AbstractProvisioner.php b/Provisioner/AbstractProvisioner.php index 8e33ed8..9e5ce14 100644 --- a/Provisioner/AbstractProvisioner.php +++ b/Provisioner/AbstractProvisioner.php @@ -30,4 +30,9 @@ public final function run() } abstract protected function doRun(); + + public function isExecutedByDefault() + { + return true; + } } diff --git a/Provisioner/CacheClearBuildProvisioner.php b/Provisioner/CacheClearBuildProvisioner.php index 22cac0c..7d18255 100644 --- a/Provisioner/CacheClearBuildProvisioner.php +++ b/Provisioner/CacheClearBuildProvisioner.php @@ -76,4 +76,9 @@ public function getName() { return 'Clear caches'; } + + public function isExecutedByDefault() + { + return false; + } } diff --git a/Provisioner/ProvisionerInterface.php b/Provisioner/ProvisionerInterface.php index f98e63a..895df1d 100644 --- a/Provisioner/ProvisionerInterface.php +++ b/Provisioner/ProvisionerInterface.php @@ -9,4 +9,5 @@ interface ProvisionerInterface public function setTask(Task $task); public function run(); public function getName(); + public function isExecutedByDefault(); } From bb83821c06c2dfc83dd59745cd05f3a160ddd284 Mon Sep 17 00:00:00 2001 From: Matthijs Van Assche Date: Fri, 27 Jul 2018 16:28:06 +0200 Subject: [PATCH 103/104] WEBDOM-395: Fixed form test. --- Tests/Form/Type/TaskFormTypeTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/Form/Type/TaskFormTypeTest.php b/Tests/Form/Type/TaskFormTypeTest.php index d5aed15..2172f34 100644 --- a/Tests/Form/Type/TaskFormTypeTest.php +++ b/Tests/Form/Type/TaskFormTypeTest.php @@ -47,6 +47,7 @@ public function testBuildForm() $mock = $this->getMockBuilder(ProvisionerInterface::class)->getMock(); $name = 'Provisioner' . $index; $mock->expects($this->once())->method('getName')->willReturn($name); + $mock->expects($this->once())->method('isExecutedByDefault')->willReturn(false); $provisioners[] = $mock; $choices[$name] = get_class($mock); } @@ -67,7 +68,9 @@ public function testBuildForm() 'multiple' => true, 'required' => false, 'choices' => $choices, - 'label' => 'Limit to following provisioners (selecting none will run all provisioners)', + 'data' => [], + 'empty_data' => [], + 'label' => 'Limit to following provisioners (selecting none will run the default provisioners)', ] ); From 1b8574d634978c7b3449afe028ea404e45327ed4 Mon Sep 17 00:00:00 2001 From: Jelle Sebreghts Date: Thu, 2 Aug 2018 15:01:46 +0200 Subject: [PATCH 104/104] Use stable version of digipolisgent/setting-bundle. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8aaa355..0a93813 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "require": { "php": ">=7.1", "symfony/symfony": ">=3.4", - "digipolisgent/setting-bundle": "dev-develop", + "digipolisgent/setting-bundle": "^1.0", "digipolisgent/command-builder": "^1.0", "doctrine/doctrine-bundle": "^1.6", "doctrine/orm": "^2.5",