Completed
Push — master ( 827038...ef4825 )
by Joschi
03:24
created

FileAdapterStrategy::getRepositorySize()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
ccs 0
cts 6
cp 0
rs 9.6666
cc 3
eloc 6
nc 2
nop 0
crap 12
1
<?php
2
3
/**
4
 * apparat-object
5
 *
6
 * @category    Apparat
7
 * @package     Apparat\Object
8
 * @subpackage  Apparat\Object\Infrastructure
9
 * @author      Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright   Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license     http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Apparat\Object\Infrastructure\Repository;
38
39
use Apparat\Kernel\Ports\Kernel;
40
use Apparat\Object\Application\Repository\AbstractAdapterStrategy;
41
use Apparat\Object\Domain\Model\Object\ResourceInterface;
42
use Apparat\Object\Domain\Model\Path\RepositoryPath;
43
use Apparat\Object\Domain\Repository\RepositoryInterface;
44
use Apparat\Object\Domain\Repository\RuntimeException;
45
use Apparat\Object\Domain\Repository\Selector;
46
use Apparat\Object\Domain\Repository\SelectorInterface;
47
use Apparat\Object\Infrastructure\Factory\ResourceFactory;
48
use Apparat\Resource\Infrastructure\Io\File\AbstractFileReaderWriter;
49
50
/**
51
 * File adapter strategy
52
 *
53
 * @package Apparat\Object
54
 * @subpackage Apparat\Object\Infrastructure
55
 */
56
class FileAdapterStrategy extends AbstractAdapterStrategy
57
{
58
    /**
59
     * Adapter strategy type
60
     *
61
     * @var string
62
     */
63
    const TYPE = 'file';
64
    /**
65
     * Configuration
66
     *
67
     * Example
68
     *
69
     * @var array
70
     */
71
    protected $config = null;
72
    /**
73
     * Root directory (without trailing directory separator)
74
     *
75
     * @var string
76
     */
77
    protected $root = null;
78
    /**
79
     * Configuration directory (including trailing directory separator)
80
     *
81
     * @var string
82
     */
83
    protected $configDir = null;
84
85
    /**
86
     * Adapter strategy constructor
87
     *
88
     * @param array $config Adapter strategy configuration
89
     * @throws InvalidArgumentException If the root directory configuration is empty
90
     * @throws InvalidArgumentException If the root directory configuration is invalid
91
     */
92 12
    public function __construct(array $config)
93
    {
94 12
        parent::__construct($config, ['root']);
95
96
        // If the root directory configuration is empty
97 11
        if (empty($this->config['root'])) {
98 1
            throw new InvalidArgumentException(
99 1
                'Empty file adapter strategy root',
100 1
                InvalidArgumentException::EMTPY_FILE_STRATEGY_ROOT
101
            );
102
        }
103
104
        // If the root directory configuration is invalid
105 10
        $this->root = realpath($this->config['root']);
106 10
        if (empty($this->root) || !@is_dir($this->root)) {
107 1
            throw new InvalidArgumentException(
108
                sprintf(
109 1
                    'Invalid file adapter strategy root "%s"',
110 1
                    $this->config['root']
111
                ),
112 1
                InvalidArgumentException::INVALID_FILE_STRATEGY_ROOT
113
            );
114
        }
115
116 9
        $this->configDir = $this->root.DIRECTORY_SEPARATOR.'.repo'.DIRECTORY_SEPARATOR;
117 9
    }
118
119
    /**
120
     * Find objects by selector
121
     *
122
     * @param Selector|SelectorInterface $selector Object selector
123
     * @param RepositoryInterface $repository Object repository
124
     * @return array[PathInterface] Object paths
0 ignored issues
show
Documentation introduced by
The doc-type array[PathInterface] could not be parsed: Expected "]" at position 2, but found "PathInterface". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
125
     */
126 7
    public function findObjectPaths(SelectorInterface $selector, RepositoryInterface $repository)
127
    {
128 7
        chdir($this->root);
129
130
        // Build a glob string from the selector
131 7
        $glob = '';
132 7
        $globFlags = GLOB_ONLYDIR | GLOB_NOSORT;
133
134 7
        $year = $selector->getYear();
135 7
        if ($year !== null) {
136 7
            $glob .= '/'.$year;
137
        }
138
139 7
        $month = $selector->getMonth();
140 7
        if ($month !== null) {
141 7
            $glob .= '/'.$month;
142
        }
143
144 7
        $day = $selector->getDay();
145 7
        if ($day !== null) {
146 7
            $glob .= '/'.$day;
147
        }
148
149 7
        $hour = $selector->getHour();
150 7
        if ($hour !== null) {
151 2
            $glob .= '/'.$hour;
152
        }
153
154 7
        $minute = $selector->getMinute();
155 7
        if ($minute !== null) {
156 2
            $glob .= '/'.$minute;
157
        }
158
159 7
        $second = $selector->getSecond();
160 7
        if ($second !== null) {
161 2
            $glob .= '/'.$second;
162
        }
163
164 7
        $uid = $selector->getId();
165 7
        $type = $selector->getType();
166 7
        if (($uid !== null) || ($type !== null)) {
167 7
            $glob .= '/'.($uid ?: Selector::WILDCARD).'.'.($type ?: Selector::WILDCARD);
168
169 7
            $revision = $selector->getRevision();
170 7
            if ($revision !== null) {
171 1
                $glob .= '/'.($uid ?: Selector::WILDCARD).'-'.$revision;
172 1
                $globFlags &= ~GLOB_ONLYDIR;
173
            }
174
        }
175
176 7
        return array_map(
177 7
            function ($objectPath) use ($repository) {
178 7
                return Kernel::create(RepositoryPath::class, [$repository, '/'.$objectPath]);
179 7
            },
180 7
            glob(ltrim($glob, '/'), $globFlags)
181
        );
182
    }
183
184
    /**
185
     * Find and return an object resource
186
     *
187
     * @param string $resourcePath Repository relative resource path
188
     * @return ResourceInterface Object resource
189
     */
190 6
    public function getObjectResource($resourcePath)
191
    {
192 6
        return ResourceFactory::create(AbstractFileReaderWriter::WRAPPER.$this->root.$resourcePath);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \Apparat\Object\I...>root . $resourcePath); (Apparat\Resource\Domain\...source\AbstractResource) is incompatible with the return type declared by the interface Apparat\Object\Domain\Re...face::getObjectResource of type Apparat\Object\Domain\Mo...bject\ResourceInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
193
    }
194
195
    /**
196
     * Return the repository size (number of objects in the repository)
197
     *
198
     * @return int Repository size
199
     */
200
    public function getRepositorySize()
201
    {
202
        $sizeDescriptorFile = $this->configDir.'size.txt';
203
        $repositorySize = 0;
204
        if (is_file($sizeDescriptorFile) && is_readable($sizeDescriptorFile)) {
205
            $repositorySize = intval(file_get_contents($this->configDir.'size.txt'));
206
        }
207
        return $repositorySize;
208
    }
209
210
    /**
211
     * Initialize the repository
212
     *
213
     * @return void
214
     * @throws RuntimeException If the repository cannot be initialized
215
     * @throws RuntimeException If the repository size descriptor can not be created
216
     */
217
    public function initializeRepository()
218
    {
219
        // If the repository cannot be initialized
220
        if (!is_dir($this->configDir) && !mkdir($this->configDir, 0777, true)) {
221
            throw new RuntimeException('Could not initialize repository', RuntimeException::REPO_NOT_INITIALIZED);
222
        }
223
224
        // If the repository size descriptor can not be created
225
        if (!@is_file($this->configDir.'size.txt') && !file_put_contents($this->configDir.'size.txt', '0')) {
226
            throw new RuntimeException(
227
                'Could not create repository size descriptor',
228
                RuntimeException::REPO_SIZE_DESCRIPTOR_NOT_CREATED
229
            );
230
        }
231
    }
232
}
233