Completed
Pull Request — master (#6706)
by Damian
09:02
created

ThemeManifest   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 176
rs 10
c 0
b 0
f 0
wmc 17
lcom 1
cbo 3

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 24 4
A getBase() 0 4 1
A getCacheKey() 0 9 1
A getThemes() 0 7 2
A regenerate() 0 17 2
A handleDirectory() 0 20 4
A init() 0 8 3
1
<?php
2
3
namespace SilverStripe\View;
4
5
use Psr\SimpleCache\CacheInterface;
6
use SilverStripe\Core\Cache\CacheFactory;
7
use SilverStripe\Core\Manifest\ManifestFileFinder;
8
9
/**
10
 * A class which builds a manifest of all themes (which is really just a directory called "templates")
11
 */
12
class ThemeManifest implements ThemeList
13
{
14
15
    const TEMPLATES_DIR = 'templates';
16
17
    /**
18
     * Base path
19
     *
20
     * @var string
21
     */
22
    protected $base;
23
24
    /**
25
     * Include tests
26
     *
27
     * @var bool
28
     */
29
    protected $tests;
30
31
    /**
32
     * Path to application code
33
     *
34
     * @var string
35
     */
36
    protected $project;
37
38
    /**
39
     * Cache
40
     *
41
     * @var CacheInterface
42
     */
43
    protected $cache;
44
45
    /**
46
     * Cache key
47
     *
48
     * @var string
49
     */
50
    protected $cacheKey;
51
52
    /**
53
     * List of theme root directories
54
     *
55
     * @var string[]
56
     */
57
    protected $themes = null;
58
59
    /**
60
     * Constructs a new template manifest. The manifest is not actually built
61
     * or loaded from cache until needed.
62
     *
63
     * @param string $base The base path.
64
     * @param string $project Path to application code
65
     *
66
     * @param bool $includeTests Include tests in the manifest.
67
     * @param bool $forceRegen Force the manifest to be regenerated.
68
     * @param CacheFactory $cacheFactory Cache factory to generate backend cache with
69
     */
70
    public function __construct(
71
        $base,
72
        $project,
73
        $includeTests = false,
74
        $forceRegen = false,
75
        CacheFactory $cacheFactory = null
76
    ) {
77
        $this->base  = $base;
78
        $this->tests = $includeTests;
79
        $this->project = $project;
80
81
        // build cache from factory
82
        if ($cacheFactory) {
83
            $this->cache = $cacheFactory->create(
84
                CacheInterface::class.'.thememanifest',
85
                [ 'namespace' => 'thememanifest' . ($includeTests ? '_tests' : '') ]
86
            );
87
        }
88
        $this->cacheKey = $this->getCacheKey();
89
90
        if ($forceRegen) {
91
            $this->regenerate();
92
        }
93
    }
94
95
    /**
96
     * @return string
97
     */
98
    public function getBase()
99
    {
100
        return $this->base;
101
    }
102
103
    /**
104
     * Generate a unique cache key to avoid manifest cache collisions.
105
     * We compartmentalise based on the base path, the given project, and whether
106
     * or not we intend to include tests.
107
     * @return string
108
     */
109
    public function getCacheKey()
110
    {
111
        return sha1(sprintf(
112
            "manifest-%s-%s-%u",
113
            $this->base,
114
            $this->project,
115
            $this->tests
116
        ));
117
    }
118
119
    public function getThemes()
120
    {
121
        if ($this->themes === null) {
122
            $this->init();
123
        }
124
        return $this->themes;
125
    }
126
127
    /**
128
     * Regenerates the manifest by scanning the base path.
129
     */
130
    public function regenerate()
131
    {
132
        $finder = new ManifestFileFinder();
133
        $finder->setOptions(array(
134
            'include_themes' => false,
135
            'ignore_dirs' => array('node_modules', THEMES_DIR),
136
            'ignore_tests'  => !$this->tests,
137
            'dir_callback'  => array($this, 'handleDirectory')
138
        ));
139
140
        $this->themes = [];
141
        $finder->find($this->base);
142
143
        if ($this->cache) {
144
            $this->cache->set($this->cacheKey, $this->themes);
145
        }
146
    }
147
148
    /**
149
     * Add a directory to the manifest
150
     *
151
     * @param string $basename
152
     * @param string $pathname
153
     * @param int $depth
154
     */
155
    public function handleDirectory($basename, $pathname, $depth)
156
    {
157
        if ($basename !== self::TEMPLATES_DIR) {
158
            return;
159
        }
160
161
        // We only want part of the full path, so split into directories
162
        $parts = explode('/', $pathname);
163
        // Take the end (the part relative to base), except the very last directory
164
        $themeParts = array_slice($parts, -$depth, $depth-1);
165
        // Then join again
166
        $path = '/'.implode('/', $themeParts);
167
168
        // If this is in the project, add to beginning of list. Else add to end.
169
        if ($themeParts && $themeParts[0] == $this->project) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $themeParts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
170
            array_unshift($this->themes, $path);
171
        } else {
172
            array_push($this->themes, $path);
173
        }
174
    }
175
176
    /**
177
     * Initialise the manifest
178
     */
179
    protected function init()
180
    {
181
        if ($this->cache && ($data = $this->cache->get($this->cacheKey))) {
182
            $this->themes = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type * is incompatible with the declared type array<integer,string> of property $themes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
183
        } else {
184
            $this->regenerate();
185
        }
186
    }
187
}
188