e26cb4198b0fa1097ed3e5da80c6322e7233290d
[civicrm-core.git] / Civi / Test / ExampleDataLoader.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 namespace Civi\Test;
13
14 class ExampleDataLoader {
15
16 /**
17 * These are "heavy" properties which are not cached. i.e.
18 * - They are generated by `$ex->build($example);`
19 * - They are not generated by '$ex->getExamples();'
20 * - They are returned by `$this->getFull()`
21 * - They are not returned by `$this->getMeta()`.
22 */
23 const HEAVY_FIELDS = 'data,asserts';
24
25 /**
26 * @var array|null
27 */
28 private $metas;
29
30 /**
31 * Get a list of all examples, including basic metadata (name, title, workflow).
32 *
33 * @return array
34 * Ex: ['my_example' => ['title' => ..., 'workflow' => ..., 'tags' => ...]]
35 * @throws \ReflectionException
36 */
37 public function getMetas(): array {
38 if ($this->metas === NULL) {
39 // $cache = new \CRM_Utils_Cache_NoCache([]);
40 $cache = \CRM_Utils_Constant::value('CIVICRM_TEST') ? new \CRM_Utils_Cache_NoCache([]) : \Civi::cache('long');
41 $cacheKey = \CRM_Utils_String::munge(__CLASS__);
42 $this->metas = $cache->get($cacheKey);
43 if ($this->metas === NULL) {
44 $this->metas = $this->findMetas();
45 $cache->set($cacheKey, $this->metas);
46 }
47 }
48 return $this->metas;
49 }
50
51 public function getMeta(string $name): ?array {
52 $all = $this->getMetas();
53 return $all[$name] ?? NULL;
54 }
55
56 /**
57 * @param string $name
58 *
59 * @return array|null
60 */
61 public function getFull(string $name): ?array {
62 $example = $this->getMeta($name);
63 if ($example === NULL) {
64 return NULL;
65 }
66
67 $obj = $this->createObj($example['file'], $example['class']);
68 $obj->build($example);
69 return $example;
70 }
71
72 /**
73 * Get a list of all examples, including basic metadata (name, title, workflow).
74 *
75 * @return array
76 * Ex: ['my_example' => ['title' => ..., 'workflow' => ..., 'tags' => ...]]
77 * @throws \ReflectionException
78 */
79 protected function findMetas(): array {
80 $classes = array_merge(
81 // This scope of search is decidedly narrow - it should probably be expanded.
82 $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/'), 'Civi/Test/ExampleData', '\\'),
83 $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/'), 'CRM/*/WorkflowMessage', '_'),
84 $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/'), 'Civi/*/WorkflowMessage', '\\'),
85 $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/'), 'Civi/WorkflowMessage', '\\'),
86 $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/tests/phpunit/'), 'CRM/*/WorkflowMessage', '_'),
87 $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/tests/phpunit/'), 'Civi/*/WorkflowMessage', '\\')
88 );
89
90 $all = [];
91 foreach ($classes as $file => $class) {
92 $obj = $this->createObj($file, $class);
93 $offset = 0;
94 foreach ($obj->getExamples() as $example) {
95 $example['file'] = $file;
96 $example['class'] = $class;
97 if (!isset($example['name'])) {
98 $example['name'] = $example['class'] . '#' . $offset;
99 }
100 $all[$example['name']] = $example;
101 $offset++;
102 }
103 }
104
105 return $all;
106 }
107
108 /**
109 * @param $classRoot
110 * Ex: Civi root dir.
111 * @param $classDir
112 * Folder to search (within the parent).
113 * @param $classDelim
114 * Namespace separator, eg underscore or backslash.
115 * @return array
116 * Array(string $includeFile => string $className).
117 */
118 private function scanExampleClasses($classRoot, $classDir, $classDelim): array {
119 $civiRoot = \Civi::paths()->getPath('[civicrm.root]/');
120 $classRoot = \CRM_Utils_File::addTrailingSlash($classRoot, '/');
121 // Prefer include-paths relative to civiRoot - eg make tests/phpunit/* loadable at runtime.
122 $includeRoot = \CRM_Utils_File::isChildPath($civiRoot, $classRoot) ? $civiRoot : $classRoot;
123
124 $r = [];
125 $exDirs = (array) glob($classRoot . $classDir);
126 foreach ($exDirs as $exDir) {
127 foreach (\CRM_Utils_File::findFiles($exDir, '*.ex.php') as $file) {
128 $file = str_replace(DIRECTORY_SEPARATOR, '/', $file);
129 $includeFile = \CRM_Utils_File::relativize($file, $includeRoot);
130 $classFile = \CRM_Utils_File::relativize($file, $classRoot);
131 $class = str_replace('/', $classDelim, preg_replace('/\.ex\.php$/', '',
132 $classFile));
133 $r[$includeFile] = $class;
134 }
135 }
136 return $r;
137 }
138
139 private function createObj(?string $file, ?string $class): ExampleDataInterface {
140 if ($file) {
141 include_once $file;
142 }
143 if (!class_exists($class)) {
144 throw new \CRM_Core_Exception("Failed to read example (class '{$class}' in file '{$file}')");
145 }
146
147 return new $class();
148 }
149
150 }