Passed
Branch v2 (d444af)
by Pieter
02:38
created

FileStorageDataLayer::retrieveAll()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 3
dl 0
loc 16
rs 9.9
c 0
b 0
f 0
1
<?php
2
namespace W2w\Lib\Apie\Retrievers;
3
4
use LimitIterator;
5
use ReflectionClass;
6
use Symfony\Component\Finder\Finder;
7
use Symfony\Component\Finder\SplFileInfo;
8
use Symfony\Component\PropertyAccess\PropertyAccessor;
9
use W2w\Lib\Apie\Exceptions\CanNotDetermineIdException;
10
use W2w\Lib\Apie\Exceptions\CouldNotMakeDirectoryException;
11
use W2w\Lib\Apie\Exceptions\CouldNotRemoveFileException;
12
use W2w\Lib\Apie\Exceptions\CouldNotWriteFileException;
13
use W2w\Lib\Apie\Exceptions\InvalidIdException;
14
use W2w\Lib\Apie\Exceptions\ResourceNotFoundException;
15
use W2w\Lib\Apie\Persisters\ApiResourcePersisterInterface;
16
use W2w\Lib\Apie\SearchFilters\SearchFilterRequest;
17
18
class FileStorageDataLayer implements ApiResourcePersisterInterface, ApiResourceRetrieverInterface, SearchFilterProviderInterface
19
{
20
    use SearchFilterFromMetadataTrait;
21
22
    private $folder;
23
24
    private $propertyAccessor;
25
26
    public function __construct(string $folder, PropertyAccessor $propertyAccessor)
27
    {
28
        $this->folder = $folder;
29
        $this->propertyAccessor = $propertyAccessor;
30
    }
31
32
    /**
33
     * Persist a new API resource. Should return the new API resource.
34
     *
35
     * @param mixed $resource
36
     * @param array $context
37
     * @return mixed
38
     */
39
    public function persistNew($resource, array $context = [])
40
    {
41
        $identifier = $context['identifier'] ?? 'id';
42
        if (!$this->propertyAccessor->isReadable($resource, $identifier)) {
43
            throw new CanNotDetermineIdException($resource, $identifier);
44
        }
45
        $id = $this->propertyAccessor->getValue($resource, $identifier);
46
        $this->store($resource, $id);
47
        return $resource;
48
49
    }
50
51
    /**
52
     * Persist an existing API resource. The input resource is the modified API resource. Should return the new API
53
     * resource.
54
     *
55
     * @param $resource
56
     * @param $int
57
     * @param array $context
58
     * @return mixed
59
     */
60
    public function persistExisting($resource, $int, array $context = [])
61
    {
62
        $identifier = $context['identifier'] ?? 'id';
63
        if ($this->propertyAccessor->isReadable($resource, $identifier)) {
64
            $actualIdentifier = $this->propertyAccessor->getValue($resource, $identifier);
65
            if ((string) $actualIdentifier !== (string) $int) {
66
                throw new InvalidIdException((string) $int);
67
            }
68
        }
69
        $this->store($resource, $int);
70
        return $resource;
71
    }
72
73
    /**
74
     * Removes an existing API resource.
75
     *
76
     * @param string $resourceClass
77
     * @param $id
78
     * @param array $context
79
     * @return mixed
80
     */
81
    public function remove(string $resourceClass, $id, array $context)
82
    {
83
        $file = $this->getFilename($resourceClass, $id);
84
        if (!@unlink($file)) {
85
            throw new CouldNotRemoveFileException($file);
86
        }
87
    }
88
89
    /**
90
     * Retrieves a single resource by some identifier.
91
     *
92
     * @param string $resourceClass
93
     * @param mixed $id
94
     * @param array $context
95
     * @return mixed
96
     */
97
    public function retrieve(string $resourceClass, $id, array $context)
98
    {
99
        $file = $this->getFilename($resourceClass, $id);
100
        if (!file_exists($file)) {
101
            throw new ResourceNotFoundException($id);
102
        }
103
        return unserialize(file_get_contents($file));
104
    }
105
106
    /**
107
     * Retrieves a list of resources with some pagination.
108
     *
109
     * @param string $resourceClass
110
     * @param array $context
111
     * @param SearchFilterRequest $searchFilterRequest
112
     * @return iterable
113
     */
114
    public function retrieveAll(string $resourceClass, array $context, SearchFilterRequest $searchFilterRequest): iterable
115
    {
116
        $offset = $searchFilterRequest->getOffset();
117
        $numberOfItems = $searchFilterRequest->getNumberOfItems();
118
        $folder = $this->getFolder($resourceClass);
119
        $result = [];
120
        $list = new LimitIterator(
121
            Finder::create()->files()->sortByName()->depth(0)->in($folder)->getIterator(),
122
            $offset,
123
            $numberOfItems
124
        );
125
        foreach ($list as $file) {
126
            /** @var SplFileInfo $file */
127
            $result[] = $this->retrieve($resourceClass, $file->getBasename(), $context);
128
        }
129
        return $result;
130
    }
131
132
    protected function getFolder(string $resourceClass): string
133
    {
134
        $refl = new ReflectionClass($resourceClass);
135
        $folder = $this->folder . DIRECTORY_SEPARATOR . $refl->getShortName();
136
        if (!is_dir($folder)) {
137
            if (!@mkdir($folder, 0777, true)) {
138
                throw new CouldNotMakeDirectoryException($folder);
139
            };
140
        }
141
        return $folder;
142
    }
143
144
    protected function getFilename(string $resourceClass, string $id): string
145
    {
146
        if (!preg_match('/^[a-zA-Z0-9_.-]+$/', $id)) {
147
            throw new InvalidIdException($id);
148
        }
149
        $folder = $this->getFolder($resourceClass);
150
151
        return $folder . DIRECTORY_SEPARATOR . $id;
152
153
    }
154
155
    private function store($resource, string $id) {
156
        $filename = $this->getFilename(get_class($resource), $id);
157
        if (false === file_put_contents($filename, serialize($resource))) {
158
            throw new CouldNotWriteFileException($filename);
159
        };
160
    }
161
}
162