Passed
Push — develop ( fc7043...b7cd40 )
by Jens
02:53
created

Repository::loadSubset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 0
loc 8
rs 9.4285
c 1
b 0
f 0
1
<?php
2
/**
3
 * User: Jens
4
 * Date: 30-1-2017
5
 * Time: 20:15
6
 *
7
 * @property array sitemap
8
 * @property array applicationComponents
9
 * @property array documentTypes
10
 * @property array bricks
11
 * @property array imageSet
12
 * @property array images
13
 * @property array files
14
 * @property array users
15
 */
16
17
namespace library\storage;
18
19
20
class Repository
21
{
22
    protected $storagePath;
23
24
    protected $fileBasedSubsets = array('sitemap', 'applicationComponents', 'documentTypes', 'bricks', 'imageSet', 'images', 'files', 'users');
25
26
    protected $sitemap;
27
    protected $sitemapChanges = false;
28
29
    protected $applicationComponents;
30
    protected $applicationComponentsChanges = false;
31
32
    protected $documentTypes;
33
    protected $documentTypesChanges = false;
34
35
    protected $bricks;
36
    protected $bricksChanges = false;
37
38
    protected $imageSet;
39
    protected $imageSetChanges = false;
40
41
    protected $images;
42
    protected $imagesChanges = false;
43
44
    protected $files;
45
    protected $filesChanges = false;
46
47
    protected $users;
48
    protected $usersChanges = false;
49
50
    protected $contentDbHandle;
51
52
    /**
53
     * Repository constructor.
54
     * @param $storagePath
55
     * @throws \Exception
56
     */
57
    public function __construct($storagePath)
58
    {
59
        $storagePath = realpath($storagePath);
60
        if (is_dir($storagePath) && $storagePath !== false) {
61
            $this->storagePath = $storagePath;
62
        } else {
63
            throw new \Exception('Repository not yet initialized.');
64
        }
65
    }
66
67
    /**
68
     * Creates the folder in which to create all storage related files
69
     *
70
     * @param $storagePath
71
     * @return bool
72
     */
73
    public static function create($storagePath)
74
    {
75
        return mkdir($storagePath);
76
    }
77
78
    /**
79
     * Initiates default storage
80
     * @throws \Exception
81
     */
82
    public function init()
83
    {
84
        $storageDefaultPath = realpath('../library/cc/install/_storage.json');
85
        $contentSqlPath = realpath('../library/cc/install/content.sql');
86
87
        $this->initConfigStorage($storageDefaultPath);
88
        $this->initContentDb($contentSqlPath);
89
90
        $this->save();
91
    }
92
93
    public function __get($name)
94
    {
95
        if (isset($this->$name)) {
96
            if (in_array($name, $this->fileBasedSubsets)) {
97
                return $this->$name;
98
            } else {
99
                dump();
100
            }
101
        } else {
102
            if (in_array($name, $this->fileBasedSubsets)) {
103
                return $this->loadSubset($name);
104
            } else {
105
                throw new \Exception('Trying to get undefined property from Repository: ' . $name);
106
            }
107
        }
108
    }
109
110
    public function __set($name, $value)
111
    {
112
        if (in_array($name, $this->fileBasedSubsets)) {
113
            $this->$name = $value;
114
            $changes = $name . 'Changes';
115
            $this->$changes = true;
116
        } else {
117
            throw new \Exception('Trying to persist unknown subset in repository: ' . $name . ' <br /><pre>' . print_r($value, true) . '</pre>');
118
        }
119
    }
120
121
    public function save()
122
    {
123
        $this->sitemapChanges ? $this->saveSubset('sitemap') : null;
124
        $this->applicationComponentsChanges ? $this->saveSubset('applicationComponents') : null;
125
        $this->documentTypesChanges ? $this->saveSubset('documentTypes') : null;
126
        $this->bricksChanges ? $this->saveSubset('bricks') : null;
127
        $this->imageSetChanges ? $this->saveSubset('imageSet') : null;
128
        $this->imagesChanges ? $this->saveSubset('images') : null;
129
        $this->filesChanges ? $this->saveSubset('files') : null;
130
        $this->usersChanges ? $this->saveSubset('users') : null;
131
    }
132
133
    protected function saveSubset($subset)
134
    {
135
        $json = json_encode($this->$subset);
136
        $subsetStoragePath = $this->storagePath . DIRECTORY_SEPARATOR . $subset . '.json';
137
        file_put_contents($subsetStoragePath, $json);
138
        $changes = $subset . 'Changes';
139
        $this->$changes = false;
140
    }
141
142
    protected function loadSubset($subset)
143
    {
144
        $subsetStoragePath = $this->storagePath . DIRECTORY_SEPARATOR . $subset . '.json';
145
        $json = file_get_contents($subsetStoragePath);
146
        $json = json_decode($json);
147
        $this->$subset = $json;
148
        return $json;
149
    }
150
151
    /**
152
     * @param $contentSqlPath
153
     */
154
    protected function initContentDb($contentSqlPath)
155
    {
156
        $db = $this->getContentDbHandle();
157
        $sql = file_get_contents($contentSqlPath);
158
        $db->exec($sql);
159
    }
160
161
    /**
162
     * @param $storageDefaultPath
163
     */
164
    protected function initConfigStorage($storageDefaultPath)
165
    {
166
        $json = file_get_contents($storageDefaultPath);
167
        $json = json_decode($json);
168
        $this->sitemap = $json->sitemap;
169
        $this->sitemapChanges = true;
170
        $this->applicationComponents = $json->applicationComponents;
171
        $this->applicationComponentsChanges = true;
172
        $this->documentTypes = $json->documentTypes;
173
        $this->documentTypesChanges = true;
174
        $this->bricks = $json->bricks;
175
        $this->bricksChanges = true;
176
        $this->imageSet = $json->imageSet;
177
        $this->imageSetChanges = true;
178
        $this->images = $json->images;
179
        $this->imagesChanges = true;
180
        $this->files = $json->files;
181
        $this->filesChanges = true;
182
        $this->users = $json->users;
183
        $this->usersChanges = true;
184
    }
185
186
    /**
187
     * @return \PDO
188
     */
189
    protected function getContentDbHandle()
190
    {
191
        if ($this->contentDbHandle === null) {
192
            $this->contentDbHandle = new \PDO('sqlite:' . $this->storagePath . DIRECTORY_SEPARATOR . 'content.db');
193
        }
194
        return $this->contentDbHandle;
195
    }
196
197
    public function getDocuments()
198
    {
199
        return $this->getDocumentsByPath('/');
200
    }
201
202
    public function getDocumentsByPath($folderPath)
203
    {
204
        $db = $this->getContentDbHandle();
205
        $folderPathWithWildcard = $folderPath . '%';
206
207
        $stmt = $this->getDbStatement('
208
            SELECT *
209
              FROM documents
210
             WHERE `path` LIKE ' . $db->quote($folderPathWithWildcard) . '
211
               AND substr(`path`, ' . (strlen($folderPath) + 1) . ') NOT LIKE "%/%"
212
               AND path != ' . $db->quote($folderPath) . '
213
          ORDER BY `type` DESC, `path` ASC
214
        ');
215
216
        $documents = $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
217
        foreach ($documents as $key => $document) {
218
            if ($document->type === 'folder') {
219
                $document->dbHandle = $db;
220
                $documents[$key] = $document;
221
            }
222
        }
223
        return $documents;
224
    }
225
226
227
    /**
228
     * @param $path
229
     * @return bool|Document
230
     */
231
    public function getDocumentContainerByPath($path)
232
    {
233
        $document = $this->getDocumentByPath($path);
234
        if ($document === false) {
235
            return false;
236
        }
237
        $slugLength = strlen($document->slug);
238
        $containerPath = substr($path, 0, -$slugLength);
239
        if ($containerPath === '/') {
240
            return $this->getRootFolder();
241
        }
242
        $containerFolder = $this->getDocumentByPath($containerPath);
243
        return $containerFolder;
244
    }
245
246
    /**
247
     * @param $path
248
     * @return bool|Document
249
     */
250
    public function getDocumentByPath($path)
251
    {
252
        $db = $this->getContentDbHandle();
253
        $document = $this->fetchDocument('
254
            SELECT *
255
              FROM documents
256
             WHERE path = ' . $db->quote($path) . '
257
        ');
258
        if ($document instanceof Document && $document->type === 'folder') {
259
            $document->dbHandle = $db;
0 ignored issues
show
Documentation introduced by
The property $dbHandle is declared protected in library\storage\Document. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
260
        }
261
        return $document;
262
    }
263
264
    protected function fetchAllDocuments($sql)
265
    {
266
        $stmt = $this->getDbStatement($sql);
267
        return $stmt->fetchAll(\PDO::FETCH_CLASS, '\library\storage\Document');
268
    }
269
270
    protected function fetchDocument($sql)
271
    {
272
        $stmt = $this->getDbStatement($sql);
273
        return $stmt->fetchObject('\library\storage\Document');
274
    }
275
276
    /**
277
     * @param $sql
278
     * @return \PDOStatement
279
     * @throws \Exception
280
     */
281
    protected function getDbStatement($sql)
282
    {
283
        $db = $this->getContentDbHandle();
284
        $stmt = $db->query($sql);
285 View Code Duplication
        if ($stmt === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
286
            $errorInfo = $db->errorInfo();
287
            $errorMsg = $errorInfo[2];
288
            throw new \Exception('SQLite Exception: ' . $errorMsg . ' in SQL: <br /><pre>' . $sql . '</pre>');
289
        }
290
        return $stmt;
291
    }
292
293
    protected function getRootFolder()
294
    {
295
        $rootFolder = new Document();
296
        $rootFolder->path = '/';
297
        $rootFolder->type = 'folder';
298
        return $rootFolder;
299
    }
300
301
    /**
302
     * @param $path
303
     * @param Document $documentObject
304
     * @return bool
305
     */
306
    public function saveDocument($documentObject)
307
    {
308
        $db = $this->getContentDbHandle();
309
        $stmt = $this->getDbStatement('
310
            INSERT OR REPLACE INTO documents (`path`,`title`,`slug`,`type`,`documentType`,`documentTypeSlug`,`state`,`lastModificationDate`,`creationDate`,`lastModifiedBy`,`fields`,`bricks`,`dynamicBricks`)
311
            VALUES(
312
              ' . $db->quote($documentObject->path) . ',
313
              ' . $db->quote($documentObject->title) . ',
314
              ' . $db->quote($documentObject->slug) . ',
315
              ' . $db->quote($documentObject->type) . ',
316
              ' . $db->quote($documentObject->documentType) . ',
317
              ' . $db->quote($documentObject->documentTypeSlug) . ',
318
              ' . $db->quote($documentObject->state) . ',
319
              ' . $db->quote($documentObject->lastModificationDate) . ',
320
              ' . $db->quote($documentObject->creationDate) . ',
321
              ' . $db->quote($documentObject->lastModifiedBy) . ',
322
              ' . $db->quote(json_encode($documentObject->fields)) . ',
0 ignored issues
show
Documentation introduced by
The property $fields is declared protected in library\storage\Document. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
323
              ' . $db->quote(json_encode($documentObject->bricks)) . ',
0 ignored issues
show
Documentation introduced by
The property $bricks is declared protected in library\storage\Document. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
324
              ' . $db->quote(json_encode($documentObject->dynamicBricks)) . '
0 ignored issues
show
Documentation introduced by
The property $dynamicBricks is declared protected in library\storage\Document. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
325
            )
326
        ');
327
        $result = $stmt->execute();
328
        return $result;
329
    }
330
331
    public function deleteDocumentByPath($path)
332
    {
333
        $db = $this->getContentDbHandle();
334
        $documentToDelete = $this->getDocumentByPath($path);
335
        if ($documentToDelete instanceof Document) {
336
            if ($documentToDelete->type == 'document') {
337
                $stmt = $this->getDbStatement('
338
                    DELETE FROM documents
339
                          WHERE path = ' . $db->quote($path) . '
340
                ');
341
            } elseif ($documentToDelete->type == 'folder') {
342
                $folderPathWithWildcard = $path . '%';
343
                $stmt = $this->getDbStatement('
344
                    DELETE FROM documents
345
                          WHERE (path LIKE ' . $db->quote($folderPathWithWildcard) . '
346
                            AND substr(`path`, ' . (strlen($path) + 1) . ', 1) = "/")
347
                            OR path = ' . $db->quote($path) . '
348
                ');
349
            }
350
        }
351
352
        $stmt->execute();
0 ignored issues
show
Bug introduced by
The variable $stmt does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
353
    }
354
}