ZeroConfDriver   B
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 260
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 91.49%

Importance

Changes 12
Bugs 7 Features 0
Metric Value
wmc 39
c 12
b 7
f 0
lcom 1
cbo 11
dl 0
loc 260
ccs 86
cts 94
cp 0.9149
rs 8.2857

11 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 18 5
B checkParams() 0 19 12
A getModelsConfigFile() 0 6 1
B getModelsDefinition() 0 28 6
B saveModelsDefinition() 0 20 5
A setDbAdapter() 0 5 1
A getDbAdapter() 0 4 1
A getMetadata() 0 12 3
A clearMetadataCache() 0 5 1
A getDefaultMetadata() 0 21 3
A setMetadata() 0 5 1
1
<?php
2
3
namespace Soluble\Normalist\Driver;
4
5
use Soluble\Schema\Source;
6
use Zend\Db\Adapter\Adapter;
7
use Zend\Config\Writer;
8
9
class ZeroConfDriver implements DriverInterface
10
{
11
    /**
12
     * @var Source\AbstractSchemaSource
13
     */
14
    protected $metadata;
15
16
    /**
17
     *
18
     * @var array
19
     */
20
    protected $params;
21
22
    /**
23
     *
24
     * @var array
25
     */
26
    protected $default_options = [
27
        'alias' => 'default',
28
        'path' => null,
29
        'version' => 'latest',
30
        'schema' => null,
31
        'permissions' => 0666
32
    ];
33
34
    /**
35
     *
36
     * @var array
37
     */
38
    protected static $metadataCache = [];
39
40
    /**
41
     * Underlying database adapter
42
     * @var Adapter
43
     */
44
    protected $adapter;
45
46
    /**
47
     * Construct a new Zero configuration driver
48
     *
49
     * $params allows you to specify the
50
     *   path    : where to store the model definition (default to sys_get_temp_dir())
51
     *   alias   : the alias to use when using multiple schemas, default: 'default'
52
     *   version : the version to use, default to 'latest'
53
     *   schema  : the database schema name, default to current adapter connection
54
     *   permissions: by default the model file is created with permission 0666
55
     *
56
     *
57
     * @param Adapter $adapter
58
     * @param array|Traversable $params [alias,path,version]
59
     * @throws Exception\ModelPathNotFoundException
60
     * @throws Exception\InvalidArgumentException
61
     */
62 151
    public function __construct(Adapter $adapter, $params = [])
63
    {
64 151
        $this->setDbAdapter($adapter);
65
66 151
        if (!is_array($params) && !$params instanceof \Traversable) {
67 1
            throw new Exception\InvalidArgumentException(__METHOD__ . ' $params parameter expects an array or Traversable object');
68
        }
69
70 151
        $this->params = array_merge($this->default_options, (array) $params);
71 151
        if ($this->params['path'] == '') {
72 151
            $this->params['path'] = sys_get_temp_dir();
73 151
        }
74 151
        if (!is_dir($this->params['path'])) {
75 1
            $path = (string) $this->params['path'];
76 1
            throw new Exception\ModelPathNotFoundException(__METHOD__ . " Model directory not found '" . $path . "'");
77
        }
78 151
        $this->checkParams();
79 151
    }
80
81
    /**
82
     * Checks model configuration params
83
     * @throws Exception\InvalidArgumentException
84
     *
85
     */
86 151
    protected function checkParams()
87
    {
88 151
        if (!is_string($this->params['alias']) || trim($this->params['alias']) == '') {
89 1
            throw new Exception\InvalidArgumentException(__METHOD__ . ' $params["alias"] parameter expects valid string');
90
        }
91 151
        if ($this->params['schema'] !== null &&
92 151
                (!is_string($this->params['schema']) || trim($this->params['schema']) == '')) {
93 1
            throw new Exception\InvalidArgumentException(__METHOD__ . ' $params["schema"] parameter expects valid string');
94
        }
95 151
        if (!is_scalar($this->params['version']) || trim($this->params['version']) == '') {
96 1
            throw new Exception\InvalidArgumentException(__METHOD__ . ' $params["version"] parameter expects valid scalar value');
97
        }
98 151
        if (!is_string($this->params['path']) || trim($this->params['path']) == '') {
99
            throw new Exception\InvalidArgumentException(__METHOD__ . ' $params["path"] parameter expects valid string value');
100
        }
101 151
        if ($this->params['permissions'] != '' && !is_scalar($this->params['permissions'])) {
102 1
            throw new Exception\InvalidArgumentException(__METHOD__ . ' $params["permission"] parameter expects string|interger|octal value');
103
        }
104 151
    }
105
106
    /**
107
     * Return models configuration file
108
     * @return string
109
     */
110 17
    public function getModelsConfigFile()
111
    {
112 17
        $o = $this->params;
113 17
        $file = $o['path'] . DIRECTORY_SEPARATOR . 'normalist_zeroconf_cache_' . $o['alias'] . '_' . $o['version'] . '.php';
114 17
        return $file;
115
    }
116
117
    /**
118
     * Get models definition according to options
119
     *
120
     * @throws Exception\ModelFileNotFoundException
121
     * @throws Exception\ModelFileCorruptedException
122
     * @return array
123
     */
124 17
    public function getModelsDefinition()
125
    {
126 17
        $file = $this->getModelsConfigFile();
127 17
        if (!file_exists($file) || !is_readable($file)) {
128 6
            throw new Exception\ModelFileNotFoundException(__METHOD__ . " Model configuration file '$file' does not exists or not readable");
129
        }
130
131 15
        if (defined('HHVM_VERSION')) {
132
            // As an 'evil' workaround, waiting for hhvm to comply
133
            // see https://github.com/facebook/hhvm/issues/1447
134
            $definition = false;
135
            $file_content = file_get_contents($file);
136
            $file_content = trim(str_replace('<?php', '', $file_content));
137
            $file_content = trim(str_replace('return array(', '$definition = array(', $file_content));
138
            eval($file_content);
139
        } else {
140 15
            $definition = include $file;
141
        }
142
143 15
        if (!$definition) {
144
            throw new Exception\ModelFileCorruptedException(__METHOD__ . " Model configuration file '$file' cannot be included");
145
        }
146 15
        if (!is_array($definition)) {
147 1
            throw new Exception\ModelFileCorruptedException(__METHOD__ . " Model configuration file '$file' was included but is not a valid array");
148
        }
149
150 15
        return $definition;
151
    }
152
153
    /**
154
     * Save model definition
155
     *
156
     * @throws Exception\ModelFileNotWritableException
157
     * @param array $models_definition
158
     * @return DriverInterface
159
     */
160 6
    protected function saveModelsDefinition(array $models_definition)
161
    {
162 6
        $file = $this->getModelsConfigFile();
163 6
        if (file_exists($file) && !is_writable($file)) {
164 2
            throw new Exception\ModelFileNotWritableException(__METHOD__ . "Model configuration file '$file' cannot be overwritten, not writable.");
165
        }
166
167
        //$config = new Config($models_defintion, true);
168 6
        $writer = new Writer\PhpArray();
169 6
        $models_definition['normalist'] = ['model_version' => Metadata\NormalistModels::VERSION];
170 6
        $writer->toFile($file, $models_definition, $exclusiveLock = true);
171 6
        $perms = $this->params['permissions'];
172 6
        if ($perms != '') {
173 6
            if (decoct(octdec($perms)) == $perms) {
174 1
                $perms = octdec($perms);
175 1
            }
176 6
            chmod($file, $perms);
177 6
        }
178 6
        return $this;
179
    }
180
181
    /**
182
     * Set underlying database adapter
183
     *
184
     * @param Adapter $adapter
185
     * @return DriverInterface
186
     */
187 151
    protected function setDbAdapter(Adapter $adapter)
188
    {
189 151
        $this->adapter = $adapter;
190 151
        return $this;
191
    }
192
193
    /**
194
     * Get underlying database adapter
195
     *
196
     * @return Adapter
197
     */
198 132
    public function getDbAdapter()
199
    {
200 132
        return $this->adapter;
201
    }
202
203
    /**
204
     * Get internal metadata reader
205
     *
206
     * @return Source\AbstractSchemaSource
207
     */
208 146
    public function getMetadata()
209
    {
210 146
        $cache_key = md5(serialize($this->params));
211 146
        if (!array_key_exists($cache_key, self::$metadataCache)) {
212 18
            if ($this->metadata === null) {
213 17
                self::$metadataCache[$cache_key] = $this->getDefaultMetadata();
214 17
            } else {
215 1
                self::$metadataCache[$cache_key] = $this->metadata;
216
            }
217 18
        }
218 146
        return self::$metadataCache[$cache_key];
219
    }
220
221
    /**
222
     *
223
     * @return ZeroConfDriver
224
     */
225 20
    public function clearMetadataCache()
226
    {
227 20
        self::$metadataCache = [];
228 20
        return $this;
229
    }
230
231
    /**
232
     *
233
     * @return Metadata\NormalistModels
234
     */
235 17
    protected function getDefaultMetadata()
236
    {
237
        try {
238 17
            $model_definition = $this->getModelsDefinition();
239 17
        } catch (Exception\ExceptionInterface $e) {
240
            // means model definition does not exists
241
            // lets load it from the current connection
242 6
            if ($this->params['schema'] == '') {
243 3
                $schema = null;
244 3
            } else {
245 3
                $schema = $this->params['schema'];
246
            }
247 6
            $conn = $this->adapter->getDriver()->getConnection()->getResource();
248 6
            $md = new Source\MysqlInformationSchema($conn, $schema);
249 6
            $model_definition = (array) $md->getSchemaConfig();
250
251
            // For later use we save the models definition
252 6
            $this->saveModelsDefinition($model_definition);
253
        }
254 17
        return new Metadata\NormalistModels($model_definition);
255
    }
256
257
    /**
258
     * Set internal metadata reader
259
     *
260
     * @param Source\AbstractSchemaSource $metadata
261
     * @return ZeroConfDriver
262
     */
263 1
    public function setMetadata(Source\AbstractSchemaSource $metadata)
264
    {
265 1
        $this->metadata = $metadata;
266 1
        return $this;
267
    }
268
}
269