Completed
Push — master ( a4b4a5...98881f )
by Douglas
02:14
created

FileRobotStore::getRobot()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 14
cp 0
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 14
nc 3
nop 0
crap 12
1
<?php
2
3
/**
4
 * (c) 2018 Douglas Reith.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
declare(strict_types=1);
10
11
namespace Reith\ToyRobot\Infrastructure\Persistence;
12
13
use Psr\Log\LoggerInterface;
14
use Reith\ToyRobot\Domain\Robot\Robot;
15
16
class FileRobotStore implements RobotStoreInterface
17
{
18
    private const FILE_VERSION = '001';
19
20
    private static $store;
21
22
    private $file;
23
24
    private $logger;
25
26
    /**
27
     * __construct.
28
     *
29
     * @param \SplFileObject $file
30
     * @param LoggerInterface|null $logger
31
     */
32 1
    private function __construct(\SplFileObject $file, ?LoggerInterface $logger = null)
33
    {
34 1
        $this->file = $file;
35 1
        $this->logger = $logger;
36 1
    }
37
38
    public function __destruct()
39
    {
40
        $this->file = null;
41
    }
42
43
    /**
44
     * @param string               $basePath
45
     * @param LoggerInterface|null $logger
46
     *
47
     * @return RobotStoreInterface
48
     */
49 1
    public static function getStore(string $basePath, ?LoggerInterface $logger = null): RobotStoreInterface
50
    {
51
        // Ensure the same store is used everywhere
52 1
        if (!self::$store) {
53 1
            self::$store = self::createStore($basePath, $logger);
54
        }
55
56 1
        return self::$store;
57
    }
58
59
    /**
60
     * @param string               $basePath
61
     * @param LoggerInterface|null $logger
62
     *
63
     * @return RobotStoreInterface
64
     */
65 1
    private static function createStore(string $basePath, ?LoggerInterface $logger): RobotStoreInterface
66
    {
67 1
        $baseDir = $basePath . DIRECTORY_SEPARATOR . 'robotstore';
68
69 1
        if (false === file_exists($baseDir)) {
70 1
            mkdir($baseDir, 0644, true);
71
        }
72
73 1
        $fileName = $baseDir . DIRECTORY_SEPARATOR . self::FILE_VERSION . '-robot.txt';
74
75 1
        if (false === file_exists($fileName)) {
76 1
            touch($fileName);
77
        }
78
79
        // c+ - for reading and writing but do not truncate
80 1
        return new static(new \SplFileObject($fileName, 'c+'), $logger);
81
    }
82
83
    /**
84
     * @return Robot|null
85
     */
86
    public function getRobot(): ?Robot
87
    {
88
        if (!$this->file->getSize()) {
89
            return null;
90
        }
91
92
        $this->file->rewind();
93
94
        $contents = $this->file->fread($this->file->getSize());
95
96
        $robot = $this->decode($contents);
97
98
        if (false === $robot) {
99
            $this->log(
100
                'There was an error getting the robot from disk. Please PLACE the robot again',
101
                'error'
102
            );
103
104
            // Empty the file
105
            $this->file->ftruncate(0);
106
            $this->file = null;
107
108
            return null;
109
        }
110
111
        return $robot;
112
    }
113
114
    /**
115
     * @param Robot $robot
116
     */
117
    public function saveRobot(Robot $robot): void
118
    {
119
        // Empty the file
120
        $this->file->ftruncate(0);
121
122
        $this->file->rewind();
123
124
        // Save the robot
125
        $bytes = $this->file->fwrite(
126
            $this->encode($robot)
127
        );
128
129
        $this->file->fflush();
130
131
        $this->log(sprintf('Wrote [%d] robot bytes to [%s]', $bytes, $this->file->getRealPath()));
132
    }
133
134
    /**
135
     * @param string $msg
136
     * @param string $level
137
     */
138
    private function log(string $msg, ?string $level = 'info'): void
139
    {
140
        if (!$this->logger) {
141
            return;
142
        }
143
144
        $this->logger->log($level, $msg);
145
    }
146
147
    /**
148
     * @param string $contents
149
     * @return mixed
150
     */
151
    private function decode(string $contents)
152
    {
153
        return unserialize($contents);
154
    }
155
156
    /**
157
     * @param Robot $robot
158
     * @return string
159
     */
160
    private function encode(Robot $robot): string
161
    {
162
        return serialize($robot);
163
    }
164
}
165