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..54013ec
--- /dev/null
+++ b/CLI/CliFactoryInterface.php
@@ -0,0 +1,17 @@
+getEnvironment();
+ /** @var AbstractApplication $application */
+ $application = $appEnv->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';
+
+ $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(sprintf('SSH login for %s@%s:%s failed.', $user, $host, $port));
+ }
+ return new RemoteCli($ssh);
+ }
+ return null;
+ }
+}
diff --git a/CLI/RemoteCli.php b/CLI/RemoteCli.php
new file mode 100644
index 0000000..3fa6144
--- /dev/null
+++ b/CLI/RemoteCli.php
@@ -0,0 +1,78 @@
+connection = $connection;
+ $this->cwd = $cwd;
+ }
+
+ /**
+ * Executes a command.
+ *
+ * @param CommandBuilder $command
+ * The command to execute.
+ *
+ * @return bool
+ * True on success, false on failure.
+ */
+ 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 : '';
+
+ return $this->connection->getExitStatus() === 0;
+ }
+
+ /**
+ * Get the output of the last execution.
+ *
+ * @return string
+ */
+ public function getLastOutput()
+ {
+ return $this->lastOutput;
+ }
+
+ public function getConnection() {
+ return $this->connection;
+ }
+}
diff --git a/CacheClearer/CacheClearerInterface.php b/CacheClearer/CacheClearerInterface.php
new file mode 100644
index 0000000..6a5b125
--- /dev/null
+++ b/CacheClearer/CacheClearerInterface.php
@@ -0,0 +1,19 @@
+getContainer()
+ ->get(TaskRunnerService::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/DependencyInjection/Compiler/CacheClearProviderCompilerPass.php b/DependencyInjection/Compiler/CacheClearProviderCompilerPass.php
new file mode 100644
index 0000000..1ade2ea
--- /dev/null
+++ b/DependencyInjection/Compiler/CacheClearProviderCompilerPass.php
@@ -0,0 +1,31 @@
+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..f95be46
--- /dev/null
+++ b/DependencyInjection/Compiler/CliFactoryProviderCompilerPass.php
@@ -0,0 +1,31 @@
+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/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/Entity/AbstractApplication.php b/Entity/AbstractApplication.php
index 3f48f8e..0f6b6e2 100644
--- a/Entity/AbstractApplication.php
+++ b/Entity/AbstractApplication.php
@@ -4,8 +4,10 @@
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\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
@@ -15,23 +17,29 @@
* @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"})
+ * @UniqueEntity(fields={"name"})]
+ * @ORM\HasLifecycleCallbacks()
*/
abstract class AbstractApplication implements TemplateInterface
{
use SettingImplementationTrait;
use IdentifiableTrait;
+ use TemplateImplementationTrait;
/**
* @var string
*
* @ORM\Column(name="name", type="string", nullable=false)
* @Assert\NotBlank()
- * @Assert\Length(min="3", max="255")
+ * @Assert\Length(min="2", max="255")
+ * @Assert\Regex(
+ * pattern="/^[a-z0-9\-]+$/",
+ * message="Name can only contain alphanumeric characters and dashes."
+ * )
*/
protected $name;
@@ -54,8 +62,6 @@ abstract class AbstractApplication implements TemplateInterface
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="ApplicationEnvironment", mappedBy="application", cascade={"all"},fetch="EAGER")
- * @Assert\Valid()
- * @Assert\NotNull()
*/
protected $applicationEnvironments;
@@ -66,6 +72,13 @@ abstract class AbstractApplication implements TemplateInterface
*/
protected $deleted = false;
+ /**
+ * @var string
+ *
+ * @ORM\Column(name="application_type",type="string")
+ */
+ protected $applicationType;
+
/**
* @return string
*/
@@ -89,7 +102,7 @@ public function __construct()
/**
* @return string
*/
- public function getName()
+ public function getName(): ?string
{
return $this->name;
}
@@ -105,16 +118,16 @@ 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, 12);
+ return substr($name, 0, 14);
}
/**
* @return string
*/
- public function getGitRepo()
+ public function getGitRepo(): ?string
{
return $this->gitRepo;
}
@@ -130,7 +143,7 @@ public function setGitRepo(string $gitRepo)
/**
* @return bool
*/
- public function isHasDatabase(): bool
+ public function isHasDatabase(): ?bool
{
return $this->hasDatabase;
}
@@ -163,7 +176,7 @@ public function removeApplicationEnvironment(ApplicationEnvironment $application
/**
* @return ArrayCollection
*/
- public function getApplicationEnvironments()
+ public function getApplicationEnvironments(): Collection
{
return $this->applicationEnvironments;
}
@@ -172,7 +185,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) {
@@ -180,16 +193,16 @@ public function getApplicationEnvironmentByEnvironmentName(string $name)
}
}
- return '';
+ return null;
}
/**
* @return array
*/
- public static function getTemplateReplacements(): array
+ public static function additionalTemplateReplacements(): array
{
+ // Backward compatibility.
return [
- 'nameCanonical()' => 'getNameCanonical()',
'serverIps(dev_environment_name)' => 'getApplicationEnvironmentByEnvironmentName(dev_environment_name).getServerIps()',
];
}
@@ -197,7 +210,7 @@ public static function getTemplateReplacements(): array
/**
* @return bool
*/
- public function isDeleted(): bool
+ public function isDeleted(): ?bool
{
return $this->deleted;
}
@@ -209,4 +222,11 @@ public function setDeleted(bool $deleted = false)
{
$this->deleted = $deleted;
}
+
+ /**
+ * @ORM\PrePersist()
+ */
+ public function prePersist(){
+ $this->applicationType = $this::getApplicationType();
+ }
}
diff --git a/Entity/ApplicationEnvironment.php b/Entity/ApplicationEnvironment.php
index 38bfe91..d596e93 100644
--- a/Entity/ApplicationEnvironment.php
+++ b/Entity/ApplicationEnvironment.php
@@ -3,8 +3,10 @@
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\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
@@ -17,6 +19,7 @@ class ApplicationEnvironment implements TemplateInterface
use SettingImplementationTrait;
use IdentifiableTrait;
+ use TemplateImplementationTrait;
/**
* @var ArrayCollection
@@ -90,26 +93,10 @@ public static function getSettingImplementationName()
return 'application_environment';
}
- /**
- * @return array
- */
- public static function getTemplateReplacements(): array
- {
- return [
- 'serverIps()' => 'getServerIps()',
- 'environmentName()' => 'getEnvironment().getName()',
- 'config(key)' => 'getConfig(key)',
- 'databaseName()' => 'getDatabaseName()',
- 'databaseUser()' => 'getDatabaseUser()',
- 'databasePassword()' => 'getDatabasePassword()',
- 'gitRef()' => 'getGitRef()',
- ];
- }
-
/**
* @return AbstractApplication
*/
- public function getApplication()
+ public function getApplication(): ?AbstractApplication
{
return $this->application;
}
@@ -125,7 +112,7 @@ public function setApplication(AbstractApplication $application = null)
/**
* @return string
*/
- public function getDatabaseName()
+ public function getDatabaseName(): ?string
{
return $this->databaseName;
}
@@ -141,7 +128,7 @@ public function setDatabaseName(string $databaseName = null)
/**
* @return string
*/
- public function getEnvironmentName()
+ public function getEnvironmentName(): ?string
{
return $this->getEnvironment()->getName();
}
@@ -149,7 +136,7 @@ public function getEnvironmentName()
/**
* @return Environment
*/
- public function getEnvironment()
+ public function getEnvironment(): ?Environment
{
return $this->environment;
}
@@ -165,7 +152,7 @@ public function setEnvironment(Environment $environment)
/**
* @return string
*/
- public function getDatabaseUser()
+ public function getDatabaseUser(): ?string
{
return $this->databaseUser;
}
@@ -181,7 +168,7 @@ public function setDatabaseUser(string $databaseUser = null)
/**
* @return string
*/
- public function getDatabasePassword()
+ public function getDatabasePassword(): ?string
{
return $this->databasePassword;
}
@@ -197,7 +184,7 @@ public function setDatabasePassword(string $databasePassword = null)
/**
* @return string
*/
- public function getGitRef()
+ public function getGitRef(): ?string
{
return $this->gitRef;
}
@@ -205,7 +192,7 @@ public function getGitRef()
/**
* @param string $gitRef
*/
- public function setGitRef(string $gitRef)
+ public function setGitRef(string $gitRef = null)
{
$this->gitRef = $gitRef;
}
@@ -213,7 +200,7 @@ public function setGitRef(string $gitRef)
/**
* @return ArrayCollection
*/
- public function getTasks()
+ public function getTasks(): Collection
{
return $this->tasks;
}
@@ -221,7 +208,7 @@ public function getTasks()
/**
* @return string
*/
- public function getDomain()
+ public function getDomain(): ?string
{
return $this->domain;
}
@@ -248,14 +235,19 @@ public function getServerIps(): string
return implode(' ', $serverIps);
}
- public function getConfig($key)
+ /**
+ * @return string
+ */
+ public function getWorkerServerIp(): string
{
- foreach ($this->getSettingDataValues() as $settingDataValue) {
- if ($settingDataValue->getSettingDataType()->getKey() == $key) {
- return $settingDataValue->getValue();
+ /** @var VirtualServer $server */
+ $servers = $this->getEnvironment()->getVirtualServers();
+ foreach ($servers as $server) {
+ if ($server->isTaskServer()) {
+ return $server->getHost();
}
}
- return '';
+ return end($servers)->getHost();
}
}
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 c76746e..f7319fc 100644
--- a/Entity/ApplicationTypeEnvironment.php
+++ b/Entity/ApplicationTypeEnvironment.php
@@ -11,7 +11,8 @@
* Class ApplicationTypeEnvironment
* @package DigipolisGent\Domainator9k\CoreBundle\Entity
*
- * @ORM\Entity()
+ * @ORM\Entity(repositoryClass="DigipolisGent\Domainator9k\CoreBundle\Repository\ApplicationTypeEnvironmentRepository")
+ * @ORM\Table(name="application_type_environment")
*/
class ApplicationTypeEnvironment
{
@@ -79,4 +80,4 @@ public function getEnvironmentName()
{
return $this->getEnvironment()->getName();
}
-}
\ No newline at end of file
+}
diff --git a/Entity/Environment.php b/Entity/Environment.php
index ac46971..53a77e1 100644
--- a/Entity/Environment.php
+++ b/Entity/Environment.php
@@ -3,8 +3,10 @@
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\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
@@ -14,11 +16,12 @@
* @ORM\Table(name="environment")
* @UniqueEntity(fields={"name"})
*/
-class Environment
+class Environment implements TemplateInterface
{
use SettingImplementationTrait;
use IdentifiableTrait;
+ use TemplateImplementationTrait;
/**
* @var string
@@ -60,6 +63,21 @@ class Environment
*/
protected $virtualServers;
+ /**
+ * @var string
+ *
+ * @ORM\Column(name="git_ref",type="string",nullable=true)
+ * @Assert\NotBlank()
+ */
+ protected $gitRef;
+
+ /**
+ * @var integer
+ *
+ * @ORM\Column(name="priority",type="integer",nullable=true)
+ */
+ protected $priority;
+
/**
* Creates a new environment.
*/
@@ -83,7 +101,7 @@ public static function getSettingImplementationName()
*
* @return string
*/
- public function getName()
+ public function getName(): ?string
{
return $this->name;
}
@@ -141,7 +159,7 @@ public function addApplicationEnvironment(ApplicationEnvironment $applicationEnv
/**
* @return ArrayCollection
*/
- public function getApplicationEnvironments()
+ public function getApplicationEnvironments(): Collection
{
return $this->applicationEnvironments;
}
@@ -157,7 +175,7 @@ public function addApplicationTypeEnvironment(ApplicationTypeEnvironment $applic
/**
* @return ArrayCollection
*/
- public function getApplicationTypeEnvironments()
+ public function getApplicationTypeEnvironments(): Collection
{
return $this->applicationTypeEnvironments;
}
@@ -174,8 +192,40 @@ public function addVirtualServer(VirtualServer $virtualServer)
/**
* @return ArrayCollection
*/
- public function getVirtualServers()
+ public function getVirtualServers(): Collection
{
return $this->virtualServers;
}
+
+ /**
+ * @return string
+ */
+ public function getGitRef(): ?string
+ {
+ return $this->gitRef;
+ }
+
+ /**
+ * @param string $gitRef
+ */
+ public function setGitRef(string $gitRef)
+ {
+ $this->gitRef = $gitRef;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPriority(): ?int
+ {
+ return $this->priority;
+ }
+
+ /**
+ * @param int $priority
+ */
+ public function setPriority(int $priority = null)
+ {
+ $this->priority = $priority;
+ }
}
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..3f0d1c8 100644
--- a/Entity/Task.php
+++ b/Entity/Task.php
@@ -16,6 +16,8 @@ class Task
const STATUS_NEW = 'new';
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';
@@ -23,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
@@ -42,19 +47,22 @@ 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="provisioners", type="simple_array", nullable=true)
+ */
+ protected $provisioners;
/**
* @var string
- *
- * @ORM\Column(name="type",type="string")
+ * @ORM\Column(name="log", type="text", nullable=true)
*/
- protected $type;
+ protected $log;
/**
* Build constructor.
@@ -65,6 +73,54 @@ public function __construct()
$this->status = self::STATUS_NEW;
}
+ /**
+ * @return ApplicationEnvironment
+ */
+ public function getApplicationEnvironment()
+ {
+ return $this->applicationEnvironment;
+ }
+
+ /**
+ * @param ApplicationEnvironment $applicationEnvironment
+ */
+ public function setApplicationEnvironment(ApplicationEnvironment $applicationEnvironment)
+ {
+ $this->applicationEnvironment = $applicationEnvironment;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * @param string $type
+ */
+ public function setType(string $type)
+ {
+ $this->type = $type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getStatus(): string
+ {
+ return $this->status;
+ }
+
+ /**
+ * @param string $status
+ */
+ public function setStatus(string $status)
+ {
+ $this->status = $status;
+ }
+
/**
* @return DateTime
*/
@@ -73,6 +129,22 @@ 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.
*
@@ -98,50 +170,84 @@ public function setLog($log)
}
/**
- * @param ApplicationEnvironment $applicationEnvironment
+ * Mark this task as in progress.
*/
- public function setApplicationEnvironment(ApplicationEnvironment $applicationEnvironment)
+ public function setInProgress()
{
- $this->applicationEnvironment = $applicationEnvironment;
+ $this->setStatus(static::STATUS_IN_PROGRESS);
}
/**
- * @return ApplicationEnvironment
+ * Mark this task as processed.
*/
- public function getApplicationEnvironment()
+ public function setProcessed()
{
- return $this->applicationEnvironment;
+ $this->setStatus(static::STATUS_PROCESSED);
}
/**
- * @return string
+ * Mark this task as failed.
*/
- public function getStatus(): string
+ public function setFailed()
{
- return $this->status;
+ $this->setStatus(static::STATUS_FAILED);
}
/**
- * @param string $status
+ * Mark this task as in cancelled.
*/
- public function setStatus(string $status)
+ public function setCancelled()
{
- $this->status = $status;
+ $this->setStatus(static::STATUS_CANCEL);
}
/**
- * @return string
+ * Check if this task is new.
+ *
+ * @return boolean
*/
- public function getType(): string
+ public function isNew()
{
- return $this->type;
+ return $this->getStatus() === static::STATUS_NEW;
}
/**
- * @param string $type
+ * Check if this task is in progress.
+ *
+ * @return boolean
*/
- public function setType(string $type)
+ public function isInProgress()
{
- $this->type = $type;
+ return $this->getStatus() === static::STATUS_IN_PROGRESS;
+ }
+
+ /**
+ * Check if this task is processed.
+ *
+ * @return boolean
+ */
+ public function isProcessed()
+ {
+ return $this->getStatus() === static::STATUS_PROCESSED;
+ }
+
+ /**
+ * Check if this task is failed.
+ *
+ * @return boolean
+ */
+ public function isFailed()
+ {
+ return $this->getStatus() === static::STATUS_FAILED;
+ }
+
+ /**
+ * Check if this task is cancelled.
+ *
+ * @return boolean
+ */
+ public function isCancelled()
+ {
+ return $this->getStatus() === static::STATUS_CANCEL;
}
}
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/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/Entity/Traits/TemplateImplementationTrait.php b/Entity/Traits/TemplateImplementationTrait.php
new file mode 100644
index 0000000..f0301be
--- /dev/null
+++ b/Entity/Traits/TemplateImplementationTrait.php
@@ -0,0 +1,199 @@
+hasMethod('additionalTemplateReplacements')) {
+ $replacements += static::additionalTemplateReplacements();
+ }
+ return $replacements;
+ }
+
+ /**
+ * 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)
+ {
+ // 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;
+ }
+
+ /**
+ * 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();
+ $parameters = [];
+ foreach ($method->getParameters() as $parameter) {
+ $parameters[] = $parameter->getName();
+ }
+ $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];
+ }
+
+ // 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 [];
+ }
+ }
+ 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--;
+ $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', 'Interface'],
+ ['', ''],
+ ReflectionClass::createFromName((string)$returnType)->getShortName()
+ )
+ );
+ // And we append the parameters of chained methods to the template.
+ $template .= ucfirst(
+ str_replace(
+ ['(,', ',)'],
+ ['(', ')'],
+ preg_replace(
+ '/\((.*)\)/',
+ '(' . $replacementParameters . ',$1)',
+ $subTemplate
+ )
+ )
+ );
+ $replacements[$template] = $replacementCallback . '.' . $replacementSubCallback;
+ }
+ return $replacements;
+ }
+}
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
{
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 552e3ee..0000000
--- a/EventListener/BuildEventListener.php
+++ /dev/null
@@ -1,50 +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)
- {
- $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 4cc6ced..0000000
--- a/EventListener/DestroyEventListener.php
+++ /dev/null
@@ -1,57 +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)
- {
- $task = $event->getTask();
- $task->setStatus(Task::STATUS_PROCESSED);
- $this->entityManager->persist($task);
- $this->entityManager->flush();
- }
-}
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/Exception/LoggedException.php b/Exception/LoggedException.php
new file mode 100644
index 0000000..ecc9af8
--- /dev/null
+++ b/Exception/LoggedException.php
@@ -0,0 +1,7 @@
+add('name');
$builder->add('prod');
+ $builder->add('gitRef');
+ $builder->add('priority');
$builder->addEventSubscriber(new SettingFormListener($this->formService));
}
diff --git a/Form/Type/TaskFormType.php b/Form/Type/TaskFormType.php
new file mode 100644
index 0000000..85e6bc7
--- /dev/null
+++ b/Form/Type/TaskFormType.php
@@ -0,0 +1,94 @@
+formService = $formService;
+ $this->taskRunnerService = $taskRunnerService;
+ }
+
+ /**
+ * @param FormBuilderInterface $builder
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ parent::buildForm($builder, $options);
+
+ switch ($options['type']) {
+ case Task::TYPE_BUILD:
+ $provisioners = $this->taskRunnerService->getBuildProvisioners();
+ break;
+
+ case Task::TYPE_DESTROY:
+ $provisioners = $this->taskRunnerService->getDestroyProvisioners();
+ break;
+
+ default:
+ $provisioners = [];
+ break;
+ }
+
+ $choices = [];
+ $defaults = [];
+ foreach ($provisioners as $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,
+ 'data' => $defaults,
+ 'empty_data' => $defaults,
+ 'label' => 'Limit to following provisioners (selecting none will run the default 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/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/Provider/CacheClearProvider.php b/Provider/CacheClearProvider.php
new file mode 100644
index 0000000..51bfcdf
--- /dev/null
+++ b/Provider/CacheClearProvider.php
@@ -0,0 +1,48 @@
+cacheClearers[$class] = $clearer;
+ }
+
+ /**
+ * @param mixed $object
+ *
+ * @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 (!isset($this->cacheClearers[$class]) && $object instanceof Proxy) {
+ $class = get_parent_class($object);
+ }
+ if (isset($this->cacheClearers[$class])) {
+ return $this->cacheClearers[$class];
+ }
+
+ throw new NoCacheClearerFoundException('No cache clearer found for ' . $class);
+ }
+}
diff --git a/Provider/CliFactoryProvider.php b/Provider/CliFactoryProvider.php
new file mode 100644
index 0000000..2eea257
--- /dev/null
+++ b/Provider/CliFactoryProvider.php
@@ -0,0 +1,69 @@
+defaultCliFactory = $defaultCliFactory;
+ }
+
+ public function registerCliFactory(CliFactoryInterface $cliFactory, $class)
+ {
+ $this->cliFactories[$class] = $cliFactory;
+ }
+
+ /**
+ * @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 (!isset($this->cliFactories[$class]) && $object instanceof Proxy) {
+ $class = get_parent_class($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/Provisioner/AbstractProvisioner.php b/Provisioner/AbstractProvisioner.php
new file mode 100644
index 0000000..9e5ce14
--- /dev/null
+++ b/Provisioner/AbstractProvisioner.php
@@ -0,0 +1,38 @@
+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();
+
+ public function isExecutedByDefault()
+ {
+ return true;
+ }
+}
diff --git a/Provisioner/CacheClearBuildProvisioner.php b/Provisioner/CacheClearBuildProvisioner.php
new file mode 100644
index 0000000..7d18255
--- /dev/null
+++ b/Provisioner/CacheClearBuildProvisioner.php
@@ -0,0 +1,84 @@
+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 {
+ $cli = $this->cliFactoryProvider->createCliFor($appEnv);
+ $result = $this->cacheClearProvider
+ ->getCacheClearerFor($application)
+ ->clearCache(
+ $appEnv,
+ $cli
+ );
+ if (!$result) {
+ $this->taskLoggerService->addErrorLogMessage($this->task, 'Cache clear failed.', 2);
+ }
+ $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) {
+ $this->taskLoggerService->addWarningLogMessage($this->task, $cliEx->getMessage(), 2);
+ }
+ }
+
+ public function getName()
+ {
+ return 'Clear caches';
+ }
+
+ public function isExecutedByDefault()
+ {
+ return false;
+ }
+}
diff --git a/Provisioner/ProvisionerInterface.php b/Provisioner/ProvisionerInterface.php
new file mode 100644
index 0000000..895df1d
--- /dev/null
+++ b/Provisioner/ProvisionerInterface.php
@@ -0,0 +1,13 @@
+createQueryBuilder('ate')
+ ->innerJoin('ate.applicationType', 'at')
+ ->andWhere('at.name = :name')
+ ->setParameter('name', $type)
+ ->getQuery()
+ ->getResult();
+ }
+}
diff --git a/Resources/config/services.yml b/Resources/config/services.yml
index 7a7c241..a0f443f 100644
--- a/Resources/config/services.yml
+++ b/Resources/config/services.yml
@@ -6,20 +6,39 @@ services:
- { 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\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'
+
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\TaskFormType:
+ tags: [form.type]
DigipolisGent\Domainator9k\CoreBundle\Form\Type\ApplicationTypeEnvironmentFormType:
- tags: [form.type]
\ No newline at end of file
+ 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\Service\TaskLoggerService'
+ DigipolisGent\Domainator9k\CoreBundle\CLI\DefaultCliFactory:
+ DigipolisGent\Domainator9k\CoreBundle\Provider\CacheClearProvider:
+ DigipolisGent\Domainator9k\CoreBundle\Provider\CliFactoryProvider:
+ arguments:
+ - '@DigipolisGent\Domainator9k\CoreBundle\CLI\DefaultCliFactory'
+
diff --git a/Resources/public/js/templatehelper.js b/Resources/public/js/templatehelper.js
new file mode 100644
index 0000000..ad20793
--- /dev/null
+++ b/Resources/public/js/templatehelper.js
@@ -0,0 +1,84 @@
+(function (document, window) {
+ window.templateHelpers = {};
+ 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;
+ }
+ });
+ });
+
+ 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 @@
+
+
diff --git a/Service/TaskLoggerService.php b/Service/TaskLoggerService.php
index 448ad75..f3da140 100644
--- a/Service/TaskLoggerService.php
+++ b/Service/TaskLoggerService.php
@@ -1,24 +1,35 @@
getLog()) {
+ $log .= PHP_EOL;
+ }
+
+ $header = trim($header);
+ $header = preg_replace('/[\r\n]+/', ' ', $header);
+ $header = '### ' . $header . ' ###';
+
+ $log .= $this->indentText($header, $indent);
+
+ $task->setLog($log);
+
+ 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): self
+ {
+ if ($log = $task->getLog()) {
+ $log .= PHP_EOL;
+ }
+
+ $message = trim($message);
+ $message = str_replace(["\r\n", "\r", "\n"], PHP_EOL, $message);
+
+ if ($type && $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): self
+ {
+ 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): self
+ {
+ 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 setTask(Task $task)
+ public function addErrorLogMessage(Task $task, string $message, int $indent = 1, bool $persist = false): self
{
- $this->task = $task;
+ return $this->addLogMessage($task, self::LOG_TYPE_ERROR, $message, $indent, $persist);
}
/**
- * @param string $line
+ * 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 addLine(string $line)
+ public function addSuccessLogMessage(Task $task, string $message, int $indent = 1, bool $persist = true): self
{
- $log = $this->task->getLog();
- $log .= $line . PHP_EOL;
+ 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): self
+ {
+ return $this->addLogMessage($task, self::LOG_TYPE_FAILED, $message, $indent, $persist);
+ }
+
+ /**
+ * 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)
+ {
+ return preg_replace_callback('/(^|[\r\n]+)(\t+)?/', function($matches) use ($indent) {
+ $suffix = '';
+
+ if ($indent) {
+ $suffix .= str_repeat("\t", $indent);
+ }
+
+ if (isset($matches[2])) {
+ $suffix .= str_repeat(' ', strlen($matches[2]));
+ }
+
+ 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));
+ }
- $this->task->setLog($log);
- $this->entityManager->persist($this->task);
- $this->entityManager->flush();
+ return $log;
}
}
diff --git a/Service/TaskRunnerService.php b/Service/TaskRunnerService.php
new file mode 100644
index 0000000..60d3adf
--- /dev/null
+++ b/Service/TaskRunnerService.php
@@ -0,0 +1,215 @@
+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)
+ {
+ $provisioners = [];
+ switch ($task->getType()) {
+ case Task::TYPE_BUILD:
+ $provisioners = $this->buildProvisioners;
+ break;
+
+ case Task::TYPE_DESTROY:
+ $provisioners = $this->destroyProvisioners;
+ break;
+
+ default:
+ throw new \InvalidArgumentException(sprintf('Task type %s is not supported.', $task->getType()));
+ }
+ try {
+ foreach ($provisioners as $provisioner) {
+ if ($task->getProvisioners() && !in_array(get_class($provisioner), $task->getProvisioners())) {
+ continue;
+ }
+ $provisioner->setTask($task);
+ $provisioner->run();
+ if ($task->isFailed()) {
+ break;
+ }
+ }
+ } catch (\Exception $ex) {
+ $task->setFailed();
+ if (!($ex instanceof LoggedException)) {
+ $this->logger
+ ->addErrorLogMessage($task, $ex->getMessage(), 2)
+ ->addFailedLogMessage($task, sprintf('Provisioner %s failed.', $provisioner->getName()));
+ }
+ }
+ }
+
+ /**
+ * 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.');
+ }
+
+ /**
+ * @return ProvisionerInterface[]
+ */
+ public function getBuildProvisioners()
+ {
+ return $this->buildProvisioners;
+ }
+
+ /**
+ * @return ProvisionerInterface[]
+ */
+ public function getDestroyProvisioners()
+ {
+ return $this->destroyProvisioners;
+ }
+}
diff --git a/Service/TemplateService.php b/Service/TemplateService.php
index e3410d9..7454f63 100644
--- a/Service/TemplateService.php
+++ b/Service/TemplateService.php
@@ -8,91 +8,254 @@
/**
* 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;
+ // Register the replacements.
+ $this->resetReplacements();
+ foreach ($entities as $type => $entity) {
+ $this->registerReplacements($type, $entity);
+ }
+
+ // Replace the tokens.
+ do {
+ $result = preg_replace_callback('#
+ \[\[
+ [ ]*
+ ([a-zA-Z][a-zA-Z0-9_]*)
+ :
+ ([a-zA-Z][a-zA-Z0-9_]*)
+ \(
+ [ ]*
+ (
+ [^,\s]+
+ (?:[ ]*,[ ]*[^,\s]+)*
+ )?
+ [ ]*
+ \)
+ [ ]*
+ \]\]
+ #x', [$this, 'doReplace'], $text);
- // Loop over all entities
- foreach ($entities as $entityPrefix => $entity) {
- if (!$entity instanceof TemplateInterface) {
- throw new TemplateException('This object doesn\'t implement the TemplateInterface');
+ if ($result === $text) {
+ break;
}
- foreach ($entity::getTemplateReplacements() as $templateReplacementKey => $templateReplacementValue) {
+ $text = $result;
+ } while (true);
- // Define the replacement arguments
- $replacementArguments = [];
+ return $text;
+ }
+
+ /**
+ * Replace a key match.
+ *
+ * @param array $matches
+ * The replacement matches.
+ *
+ * @return string
+ * The replacement text.
+ */
+ protected function doReplace(array $matches): string
+ {
+ // Use readable variables names.
+ $matches[] = null;
+ list ($original, $type, $key, $params) = $matches;
- preg_match('#\((.*?)\)#', $templateReplacementKey, $match);
- if (isset($match[1]) && $match[1] != '') {
- $replacementArguments = explode(',', $match[1]);
+ 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);
+ // Create an associative array.
+ $params = array_combine($replacement['params'], $params);
+ }
+
+ $result = $replacement['object'];
- // 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);
+ foreach ($replacement['callbacks'] as $callback => $callbackParams) {
+ // Get the parameters.
+ foreach ($callbackParams as $name => &$value) {
+ if (array_key_exists($name, $params)) {
+ $value = $params[$name];
+ }
}
- // Check if the pattern exists in our text
- $hasMatch = preg_match('/' . $pattern . '/', $text, $matches);
+ // Execute the callback.
+ $result = call_user_func_array([$result, $callback], $callbackParams);
+ }
+
+ return $result;
+ }
- // If we have a match for the pattern we substitute it
- if ($hasMatch) {
- $hasMatches = true;
+ return $original;
+ }
- // The value can be called recursive
- $passingValue = $entity;
+ /**
+ * Reset the replacements.
+ */
+ protected function resetReplacements()
+ {
+ if ($this->replacements === null) {
+ $this->replacements = [];
+ $this->registerReplacements('token', $this->tokenService);
+ } else {
+ $this->replacements = [
+ 'token' => $this->replacements['token'],
+ ];
+ }
+ }
- // Get a key value pair of all arguments
- foreach ($replacementArguments as $key => $value) {
- $replacementArguments[$value] = $matches[$key + 1];
- }
+ /**
+ * 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 all functions that should be executed
- $functions = explode('.', $templateReplacementValue);
+ // Get the default replacements.
+ if ($replacements === null) {
+ if ($object instanceof TemplateInterface) {
+ $replacements = $object::getTemplateReplacements();
+ } elseif ($object instanceof TokenService) {
+ $replacements = $object->getTemplateReplacements();
+ } else {
+ 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_]*)*
+ )?
+ [ ]*
+ \)
+ $#x', $replacementKey, $matches)) {
+ continue;
+ }
- // Execute these functions on the defined entity with the discovered arguments
- foreach ($functions as $function) {
- preg_match('/^([a-zA-Z]*)(\((.*)\))?/', $function, $result);
+ $key = $matches[1];
- $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];
- }
- }
+ // 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_]*)*
+ )?
+ [ ]*
+ \)
+ $#x', $callback, $matches)) {
+ continue 2;
+ }
- $passingValue = call_user_func_array(array($passingValue, $methodName), $functionArguments);
+ // Prepare the parameters.
+ if (isset($matches[2])) {
+ $params = explode(',', str_replace(' ', '', $matches[2]));
+
+ if (array_diff($params, $keyParams)) {
+ throw new TemplateException('The replacement value callback uses unknown parameters.');
}
- // Replace the pattern with the found value
- $text = preg_replace('/' . $pattern . '/', $passingValue, $text);
+ $params = array_fill_keys($params, null);
+ } else {
+ $params = [];
}
+
+ $callbacks[$matches[1]] = $params;
}
- }
- // Recursivly go trough this function until no matches are found
- if ($hasMatches) {
- $text = $this->replaceKeys($text, $entities);
+ // Add the replacement.
+ $this->replacements[$type][$key] = [
+ 'params' => $keyParams,
+ 'callbacks' => $callbacks,
+ 'object' => $object,
+ ];
}
-
- return $text;
}
+
}
diff --git a/Service/TokenService.php b/Service/TokenService.php
new file mode 100644
index 0000000..66e82e2
--- /dev/null
+++ b/Service/TokenService.php
@@ -0,0 +1,67 @@
+repository = $entityManager->getRepository(Token::class);
+ $this->caseTransformer = new CaseTransformer(new SnakeCase(), new StudlyCaps());
+ }
+
+ public function getTemplateReplacements(): array
+ {
+ $tokens = $this->repository->findAll();
+ $replacements = [];
+ foreach ($tokens as $token) {
+ $replacements[$token->getName() . '()'] = 'get' . $this->caseTransformer->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 = $this->getTemplateReplacements();
+ $tokenName = array_search($name . '()', $replacements);
+ 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) {
+ throw new \BadMethodCallException('Token ' . $tokenName . ' not found');
+ }
+
+ return $token->getValue();
+ }
+}
diff --git a/Tests/CLI/DefaultCliFactoryTest.php b/Tests/CLI/DefaultCliFactoryTest.php
new file mode 100644
index 0000000..efc5abe
--- /dev/null
+++ b/Tests/CLI/DefaultCliFactoryTest.php
@@ -0,0 +1,68 @@
+keyCreated = true;
+ }
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+ if ($this->keyCreated) {
+ unlink(rtrim(Path::getHomeDirectory(), '/') . '/.ssh/id_rsa');
+ }
+ }
+
+ public function testCreate()
+ {
+ // 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.
+ $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 \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..8aff8d7
--- /dev/null
+++ b/Tests/CLI/RemoteCliTest.php
@@ -0,0 +1,33 @@
+getMockBuilder(SSH2::class)->disableOriginalConstructor()->getMock();
+ $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($command)
+ ->willReturn($output);
+ $connection
+ ->expects($this->at(1))
+ ->method('getExitStatus')
+ ->willReturn(0);
+ $cli = new RemoteCli($connection, $dir);
+ $this->assertEquals(true, $cli->execute(CommandBuilder::create($execute)));
+ $this->assertEquals("Executing $command\n$output", $cli->getLastOutput());
+ }
+}
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);
- }
-}
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/Entity/AbstractApplicationTest.php b/Tests/Entity/AbstractApplicationTest.php
index 4491da3..3ea35aa 100644
--- a/Tests/Entity/AbstractApplicationTest.php
+++ b/Tests/Entity/AbstractApplicationTest.php
@@ -15,7 +15,22 @@ class AbstractApplicationTest extends TestCase
public function testGetTemplateReplacements()
{
$expected = [
+ '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()',
+ '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()',
];
@@ -46,7 +61,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);
@@ -64,7 +79,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..4d93f4f 100644
--- a/Tests/Entity/ApplicationEnvironmentTest.php
+++ b/Tests/Entity/ApplicationEnvironmentTest.php
@@ -23,12 +23,22 @@ public function testTemplateReplacements()
{
$expected = [
'serverIps()' => 'getServerIps()',
- 'environmentName()' => 'getEnvironment().getName()',
+ 'workerServerIp()' => 'getWorkerServerIp()',
+ '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()',
'databasePassword()' => 'getDatabasePassword()',
'gitRef()' => 'getGitRef()',
+ 'domain()' => 'getDomain()',
+ 'applicationConfig(key)' => 'getApplication().getConfig(key)'
];
$this->assertEquals($expected, ApplicationEnvironment::getTemplateReplacements());
diff --git a/Tests/Entity/TokenTest.php b/Tests/Entity/TokenTest.php
new file mode 100644
index 0000000..a83d8b0
--- /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/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/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);
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;
}
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..2172f34
--- /dev/null
+++ b/Tests/Form/Type/TaskFormTypeTest.php
@@ -0,0 +1,84 @@
+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);
+ $mock->expects($this->once())->method('isExecutedByDefault')->willReturn(false);
+ $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,
+ 'data' => [],
+ 'empty_data' => [],
+ 'label' => 'Limit to following provisioners (selecting none will run the default provisioners)',
+ ]
+ );
+
+ $formBuilder
+ ->expects($this->at(1))
+ ->method('addEventSubscriber');
+
+ $formType = new TaskFormType($this->getFormServiceMock(), $taskRunnerService);
+ $formType->buildForm($formBuilder, ['type' => Task::TYPE_BUILD]);
+ }
+}
diff --git a/Tests/Form/Type/TokenFormTypeTest.php b/Tests/Form/Type/TokenFormTypeTest.php
new file mode 100644
index 0000000..78abec7
--- /dev/null
+++ b/Tests/Form/Type/TokenFormTypeTest.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/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..649e9cd
--- /dev/null
+++ b/Tests/Provisioner/CacheClearBuildProvisionerTest.php
@@ -0,0 +1,62 @@
+getMockBuilder(TaskLoggerService::class)->disableOriginalConstructor()->getMock();
+ $provisioner = new CacheClearBuildProvisioner($cliFactoryProvider, $cacheClearProvider, $taskLoggerService);
+ $this->assertEquals('Clear caches', $provisioner->getName());
+ }
+
+ public function testRun()
+ {
+ $cliFactoryProvider = new CliFactoryProvider();
+ $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);
+
+ $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)->willReturn(true);
+
+ $cacheClearProvider->registerCacheClearer($clearer, get_class($application));
+
+ $taskLoggerService = $this->getMockBuilder(TaskLoggerService::class)->disableOriginalConstructor()->getMock();
+
+ $provisioner = new CacheClearBuildProvisioner($cliFactoryProvider, $cacheClearProvider, $taskLoggerService);
+ $provisioner->setTask($task);
+ $provisioner->run();
+ }
+}
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;
- }
-
-}
diff --git a/Tests/Service/TaskRunnerServiceTest.php b/Tests/Service/TaskRunnerServiceTest.php
new file mode 100644
index 0000000..6b3dcf0
--- /dev/null
+++ b/Tests/Service/TaskRunnerServiceTest.php
@@ -0,0 +1,388 @@
+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('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')
+ ->willReturnCallback(function () {
+ $this->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('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)
+ ->willReturn(null);
+ $mock->expects($this->once())
+ ->method('run')
+ ->willReturnCallback(
+ function ()
+ {
+ $this->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('setTask')
+ ->with($this->task)
+ ->willReturn(null);
+ $mock->expects($this->once())
+ ->method('run')
+ ->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('setTask')
+ ->with($this->task)
+ ->willReturn(null);
+ $mock->expects($this->once())
+ ->method('run')
+ ->with()
+ ->willReturn(null);
+ $destroyProvisioners[] = $mock;
+ }
+ break;
+ }
+
+ $this->taskRunnerService = new TaskRunnerService(
+ $buildProvisioners,
+ $destroyProvisioners,
+ $this->entityManager,
+ $this->logger
+ );
+ }
+}
diff --git a/Tests/Service/TestTemplateServiceTest.php b/Tests/Service/TemplateServiceTest.php
similarity index 52%
rename from Tests/Service/TestTemplateServiceTest.php
rename to Tests/Service/TemplateServiceTest.php
index 3130563..2550b81 100644
--- a/Tests/Service/TestTemplateServiceTest.php
+++ b/Tests/Service/TemplateServiceTest.php
@@ -3,22 +3,54 @@
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
{
+ protected $token;
+ protected $tokenService;
+ protected $repository;
+
+ 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);
+ }
+
/**
* @expectedException \DigipolisGent\Domainator9k\CoreBundle\Exception\TemplateException
*/
public function testReplaceKeysWithInvalidEntity()
{
- $templateService = new TemplateService();
+ $templateService = new TemplateService($this->tokenService);
$text = <<tokenService);
+ $name = $this->token->getName();
+ $value = $this->token->getValue();
$text = <<assertEquals($expected, $actual);
@@ -62,7 +97,9 @@ public function testReplaceKeysWithValidEntity()
public function testReplaceKeysRecursively()
{
- $templateService = new TemplateService();
+ $templateService = new TemplateService($this->tokenService);
+ $name = $this->token->getName();
+ $value = $this->token->getValue();
$text = <<setTitle('Qux title example');
+ $qux->setTitle("[[ token:{$name}() ]]");
$qux->setSubTitle('[[ foo:primary() ]]');
$foo = new Foo();
@@ -85,7 +122,7 @@ public function testReplaceKeysRecursively()
$actual = $templateService->replaceKeys($text, $entities);
$expected = <<repository = $this
+ ->getMockBuilder(EntityRepository::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $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 testGetTemplateReplacements()
+ {
+ $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);
+ $value = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);
+ $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());
+ }
+
+ public function testMagicCallMethod()
+ {
+ $name = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);
+ $value = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz"), 0, 10);
+ $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}());
+ }
+}
diff --git a/Twig/TemplateHelpExtension.php b/Twig/TemplateHelpExtension.php
new file mode 100644
index 0000000..00fb1b5
--- /dev/null
+++ b/Twig/TemplateHelpExtension.php
@@ -0,0 +1,62 @@
+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,
+ ]
+ );
+ }
+}
diff --git a/composer.json b/composer.json
index dcc2ed3..0a93813 100644
--- a/composer.json
+++ b/composer.json
@@ -9,16 +9,19 @@
"require": {
"php": ">=7.1",
"symfony/symfony": ">=3.4",
- "digipolisgent/setting-bundle": "^1.0.0@alpha",
+ "digipolisgent/setting-bundle": "^1.0",
+ "digipolisgent/command-builder": "^1.0",
"doctrine/doctrine-bundle": "^1.6",
"doctrine/orm": "^2.5",
"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",
+ "roave/better-reflection": "^3"
},
"require-dev": {
- "phpunit/phpunit": "6.5"
+ "phpunit/phpunit": "^6.5"
},
"autoload": {
"psr-4": {
diff --git a/phpunit.xml b/phpunit.xml
index d84bd06..e6dbf79 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,4 +1,11 @@
-
+
./Tests