Completed
Push — master ( e31061...1587a0 )
by Joschi
02:44
created

FileAdapterStrategy::createObjectResource()   B

Complexity

Conditions 5
Paths 43

Size

Total Lines 52
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5.8054

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 52
ccs 15
cts 22
cp 0.6818
rs 8.6868
cc 5
eloc 23
nc 43
nop 1
crap 5.8054

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Id;
42
use Apparat\Object\Domain\Model\Object\ObjectInterface;
43
use Apparat\Object\Domain\Model\Object\ResourceInterface;
44
use Apparat\Object\Domain\Model\Path\PathInterface;
45
use Apparat\Object\Domain\Model\Path\RepositoryPath;
46
use Apparat\Object\Domain\Repository\RepositoryInterface;
47
use Apparat\Object\Domain\Repository\RuntimeException;
48
use Apparat\Object\Domain\Repository\Selector;
49
use Apparat\Object\Domain\Repository\SelectorInterface;
50
use Apparat\Object\Infrastructure\Factory\ResourceFactory;
51
use Apparat\Resource\Infrastructure\Io\File\AbstractFileReaderWriter;
52
53
/**
54
 * File adapter strategy
55
 *
56
 * @package Apparat\Object
57
 * @subpackage Apparat\Object\Infrastructure
58
 */
59
class FileAdapterStrategy extends AbstractAdapterStrategy
60
{
61
    /**
62
     * Adapter strategy type
63
     *
64
     * @var string
65
     */
66
    const TYPE = 'file';
67
    /**
68
     * Configuration
69
     *
70
     * @var array
71
     */
72
    protected $config = null;
73
    /**
74
     * Root directory (without trailing directory separator)
75
     *
76
     * @var string
77
     */
78
    protected $root = null;
79
    /**
80
     * Configuration directory (including trailing directory separator)
81
     *
82
     * @var string
83
     */
84
    protected $configDir = null;
85
86
    /**
87
     * Adapter strategy constructor
88
     *
89
     * @param array $config Adapter strategy configuration
90
     * @throws InvalidArgumentException If the root directory configuration is empty
91
     * @throws InvalidArgumentException If the root directory configuration is invalid
92
     */
93 14
    public function __construct(array $config)
94
    {
95 14
        parent::__construct($config, ['root']);
96
97
        // If the root directory configuration is empty
98 13
        if (empty($this->config['root'])) {
99 1
            throw new InvalidArgumentException(
100 1
                'Empty file adapter strategy root',
101 1
                InvalidArgumentException::EMTPY_FILE_STRATEGY_ROOT
102
            );
103
        }
104
105
        // Get the real path of the root directory
106 12
        $this->root = realpath($this->config['root']);
107
108
        // If the repository should be initialized
109 12
        if (!empty($this->config['init'])
110 12
            && (boolean)$this->config['init']
111 12
            && $this->initializeRepository()
112
        ) {
113 2
            $this->root = realpath($this->config['root']);
114
        }
115
116
        // If the root directory configuration is still invalid
117 12
        if (empty($this->root) || !@is_dir($this->root)) {
118 1
            throw new InvalidArgumentException(
119
                sprintf(
120 1
                    'Invalid file adapter strategy root "%s"',
121 1
                    $this->config['root']
122
                ),
123 1
                InvalidArgumentException::INVALID_FILE_STRATEGY_ROOT
124
            );
125
        }
126
127 11
        $this->configDir = $this->root.DIRECTORY_SEPARATOR.'.repo'.DIRECTORY_SEPARATOR;
128 11
    }
129
130
    /**
131
     * Initialize the repository
132
     *
133
     * @return boolean Success
134
     * @throws RuntimeException If the repository cannot be initialized
135
     * @throws RuntimeException If the repository size descriptor can not be created
136
     */
137 2
    public function initializeRepository()
138
    {
139 2
        $configDir = $this->config['root'].DIRECTORY_SEPARATOR.'.repo'.DIRECTORY_SEPARATOR;
140
141
        // If the repository cannot be initialized
142 2
        if (!is_dir($configDir) && !mkdir($configDir, 0777, true)) {
143
            throw new RuntimeException('Could not initialize repository', RuntimeException::REPO_NOT_INITIALIZED);
144
        }
145
146
        // If the repository size descriptor can not be created
147 2
        if (!@is_file($configDir.'size.txt') && !file_put_contents($configDir.'size.txt', '0')) {
148
            throw new RuntimeException(
149
                'Could not create repository size descriptor',
150
                RuntimeException::REPO_SIZE_DESCRIPTOR_NOT_CREATED
151
            );
152
        }
153
154 2
        return true;
155
    }
156
157
    /**
158
     * Find objects by selector
159
     *
160
     * @param Selector|SelectorInterface $selector Object selector
161
     * @param RepositoryInterface $repository Object repository
162
     * @return PathInterface[] Object paths
163
     */
164 7
    public function findObjectPaths(SelectorInterface $selector, RepositoryInterface $repository)
165
    {
166 7
        chdir($this->root);
167
168
        // Build a glob string from the selector
169 7
        $glob = '';
170 7
        $globFlags = GLOB_ONLYDIR | GLOB_NOSORT;
171
172 7
        $year = $selector->getYear();
173 7
        if ($year !== null) {
174 7
            $glob .= '/'.$year;
175
        }
176
177 7
        $month = $selector->getMonth();
178 7
        if ($month !== null) {
179 7
            $glob .= '/'.$month;
180
        }
181
182 7
        $day = $selector->getDay();
183 7
        if ($day !== null) {
184 7
            $glob .= '/'.$day;
185
        }
186
187 7
        $hour = $selector->getHour();
188 7
        if ($hour !== null) {
189 2
            $glob .= '/'.$hour;
190
        }
191
192 7
        $minute = $selector->getMinute();
193 7
        if ($minute !== null) {
194 2
            $glob .= '/'.$minute;
195
        }
196
197 7
        $second = $selector->getSecond();
198 7
        if ($second !== null) {
199 2
            $glob .= '/'.$second;
200
        }
201
202 7
        $uid = $selector->getId();
203 7
        $type = $selector->getType();
204 7
        if (($uid !== null) || ($type !== null)) {
205 7
            $glob .= '/'.($uid ?: Selector::WILDCARD).'.'.($type ?: Selector::WILDCARD);
206
207 7
            $revision = $selector->getRevision();
208 7
            if ($revision !== null) {
209 1
                $glob .= '/'.($uid ?: Selector::WILDCARD).'-'.$revision;
210 1
                $globFlags &= ~GLOB_ONLYDIR;
211
            }
212
        }
213
214 7
        return array_map(
215 7
            function ($objectPath) use ($repository) {
216 7
                return Kernel::create(RepositoryPath::class, [$repository, '/'.$objectPath]);
217 7
            },
218 7
            glob(ltrim($glob, '/'), $globFlags)
219
        );
220
    }
221
222
    /**
223
     * Find and return an object resource
224
     *
225
     * @param string $resourcePath Repository relative resource path
226
     * @return ResourceInterface Object resource
227
     */
228 6
    public function getObjectResource($resourcePath)
229
    {
230 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\Applicati...face::getObjectResource of type Symfony\Component\Config...ource\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...
231
    }
232
233
    /**
234
     * Allocate an object ID and create an object resource
235
     *
236
     * @param \Closure $creation Object creation closure
237
     * @return ObjectInterface Object
238
     */
239 1
    public function createObjectResource(\Closure $creation)
240
    {
241 1
        $sizeDescriptor = null;
242
243
        try {
244
            // Open the size descriptor
245 1
            $sizeDescriptor = fopen($this->configDir.'size.txt', 'r+');
246
247
            // If a lock of the size descriptor can be acquired
248 1
            if (flock($sizeDescriptor, LOCK_EX)) {
249
                // Determine the current repository size
250 1
                $repositorySize = '';
251 1
                while (!feof($sizeDescriptor)) {
252 1
                    $repositorySize .= fread($sizeDescriptor, 8);
253
                }
254 1
                $repositorySize = intval(trim($repositorySize));
255
256
                // Instantiate the next consecutive object ID
257 1
                $nextObjectId = Kernel::create(Id::class, [++$repositorySize]);
258
259
                // Create the object
260 1
                $object = $creation($nextObjectId);
261
262
                // TODO: Resource creation and persistence
263
264
                // Dump the new repository size, unlock the size descriptor
265 1
                ftruncate($sizeDescriptor, 0);
266 1
                fwrite($sizeDescriptor, $repositorySize);
267 1
                fflush($sizeDescriptor);
268 1
                flock($sizeDescriptor, LOCK_UN);
269
270
                // Return the newly created object
271 1
                return $object;
272
            }
273
274
            // Throw an error if no object could be created
275
            throw new RuntimeException(
276
                'The repository size descriptor is unlockable',
277
                RuntimeException::REPO_SIZE_DESCRIPTOR_UNLOCKABLE
278
            );
279
280
            // If any exception is thrown
281
        } catch (\Exception $e) {
282
            // Release the size descriptor lock
283
            if (is_resource($sizeDescriptor)) {
284
                flock($sizeDescriptor, LOCK_UN);
285
            }
286
287
            // Forward the thrown exception
288
            throw $e;
289
        }
290
    }
291
292
    /**
293
     * Return the repository size (number of objects in the repository)
294
     *
295
     * @return int Repository size
296
     */
297
    public function getRepositorySize()
298
    {
299
        $sizeDescriptorFile = $this->configDir.'size.txt';
300
        $repositorySize = 0;
301
        if (is_file($sizeDescriptorFile) && is_readable($sizeDescriptorFile)) {
302
            $repositorySize = intval(file_get_contents($this->configDir.'size.txt'));
303
        }
304
        return $repositorySize;
305
    }
306
}
307