CRM-17860 - CiviTester - Split into separate class files
[civicrm-core.git] / Civi / Test / CiviEnvBuilder.php
1 <?php
2 namespace Civi\Test;
3
4 use Civi\Test\CiviEnvBuilder\CallbackStep;
5 use Civi\Test\CiviEnvBuilder\ExtensionStep;
6 use Civi\Test\CiviEnvBuilder\SqlFileStep;
7 use Civi\Test\CiviEnvBuilder\SqlStep;
8 use Civi\Test\CiviEnvBuilder\StepInterface;
9 use RuntimeException;
10
11 /**
12 * Class CiviEnvBuilder
13 *
14 * Provides a fluent interface for tracking a set of steps.
15 * By computing and storing a signature for the list steps, we can
16 * determine whether to (a) do nothing with the list or (b)
17 * reapply all the steps.
18 */
19 class CiviEnvBuilder {
20 protected $name;
21
22 private $steps = array();
23
24 /**
25 * @var string|NULL
26 * A digest of the values in $steps.
27 */
28 private $targetSignature = NULL;
29
30 public function __construct($name) {
31 $this->name = $name;
32 }
33
34 public function addStep(StepInterface $step) {
35 $this->targetSignature = NULL;
36 $this->steps[] = $step;
37 return $this;
38 }
39
40 public function callback($callback, $signature = NULL) {
41 return $this->addStep(new CallbackStep($callback, $signature));
42 }
43
44 public function sql($sql) {
45 return $this->addStep(new SqlStep($sql));
46 }
47
48 public function sqlFile($file) {
49 return $this->addStep(new SqlFileStep($file));
50 }
51
52 /**
53 * Require an extension (based on its name).
54 *
55 * @param string $name
56 * @return \CiviEnvBuilder
57 */
58 public function ext($name) {
59 return $this->addStep(new ExtensionStep($name));
60 }
61
62 /**
63 * Require an extension (based on its directory).
64 *
65 * @param $dir
66 * @return \CiviEnvBuilder
67 * @throws \CRM_Extension_Exception_ParseException
68 */
69 public function extDir($dir) {
70 while ($dir && dirname($dir) !== $dir && !file_exists("$dir/info.xml")) {
71 $dir = dirname($dir);
72 }
73 if (file_exists("$dir/info.xml")) {
74 $info = \CRM_Extension_Info::loadFromFile("$dir/info.xml");
75 $name = $info->key;
76 }
77 return $this->addStep(new ExtensionStep($name));
78 }
79
80 protected function assertValid() {
81 foreach ($this->steps as $step) {
82 if (!$step->isValid()) {
83 throw new RuntimeException("Found invalid step: " . var_dump($step, 1));
84 }
85 }
86 }
87
88 /**
89 * @return string
90 */
91 protected function getTargetSignature() {
92 if ($this->targetSignature === NULL) {
93 $buf = '';
94 foreach ($this->steps as $step) {
95 $buf .= $step->getSig();
96 }
97 $this->targetSignature = md5($buf);
98 }
99
100 return $this->targetSignature;
101 }
102
103 /**
104 * @return string
105 */
106 protected function getSavedSignature() {
107 $liveSchemaRev = NULL;
108 $pdo = \Civi\Test::pdo();
109 $pdoStmt = $pdo->query(sprintf(
110 "SELECT rev FROM %s.civitest_revs WHERE name = %s",
111 \Civi\Test::dsn('database'),
112 $pdo->quote($this->name)
113 ));
114 foreach ($pdoStmt as $row) {
115 $liveSchemaRev = $row['rev'];
116 }
117 return $liveSchemaRev;
118 }
119
120 /**
121 * @param $newSignature
122 */
123 protected function setSavedSignature($newSignature) {
124 $pdo = \Civi\Test::pdo();
125 $query = sprintf(
126 'INSERT INTO %s.civitest_revs (name,rev) VALUES (%s,%s) '
127 . 'ON DUPLICATE KEY UPDATE rev = %s;',
128 \Civi\Test::dsn('database'),
129 $pdo->quote($this->name),
130 $pdo->quote($newSignature),
131 $pdo->quote($newSignature)
132 );
133
134 if (\Civi\Test::execute($query) === FALSE) {
135 throw new RuntimeException("Failed to flag schema version: $query");
136 }
137 }
138
139 /**
140 * Determine if the schema is correct. If necessary, destroy and recreate.
141 *
142 * @param bool $force
143 * @return $this
144 */
145 public function apply($force = FALSE) {
146 $dbName = \Civi\Test::dsn('database');
147 $query = "USE {$dbName};"
148 . "CREATE TABLE IF NOT EXISTS civitest_revs (name VARCHAR(64) PRIMARY KEY, rev VARCHAR(64));";
149
150 if (\Civi\Test::execute($query) === FALSE) {
151 throw new \RuntimeException("Failed to flag schema version: $query");
152 }
153
154 $this->assertValid();
155
156 if (!$force && $this->getSavedSignature() === $this->getTargetSignature()) {
157 return $this;
158 }
159 foreach ($this->steps as $step) {
160 $step->run($this);
161 }
162 $this->setSavedSignature($this->getTargetSignature());
163 return $this;
164 }
165
166 }