Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.26% covered (success)
97.26%
71 / 73
81.82% covered (success)
81.82%
9 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
File
97.26% covered (success)
97.26%
71 / 73
81.82% covered (success)
81.82%
9 / 11
49
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDir
83.33% covered (success)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getDir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 save
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getById
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getByType
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 has
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 delete
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 clear
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
7.14
 encodeValue
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 decodeValue
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Debug\Storage;
15
16/**
17 * Debug file storage class
18 *
19 * @category   Pop
20 * @package    Pop\Debug
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    2.0.0
25 */
26class File extends AbstractStorage
27{
28
29    /**
30     * Storage dir
31     * @var ?string
32     */
33    protected ?string $dir = null;
34
35    /**
36     * Constructor
37     *
38     * Instantiate the file storage object
39     *
40     * @param  string  $dir
41     * @param  ?string $format
42     */
43    public function __construct(string $dir, ?string $format = null)
44    {
45        parent::__construct($format);
46        $this->setDir($dir);
47    }
48
49    /**
50     * Set the current storage dir
51     *
52     * @param  string $dir
53     * @throws Exception
54     * @return File
55     */
56    public function setDir(string $dir): File
57    {
58        if (!file_exists($dir)) {
59            throw new Exception('Error: That directory does not exist.');
60        } else if (!is_writable($dir)) {
61            throw new Exception('Error: That directory is not writable.');
62        }
63
64        $this->dir = realpath($dir);
65
66        return $this;
67    }
68
69    /**
70     * Get the storage dir
71     *
72     * @return ?string
73     */
74    public function getDir(): ?string
75    {
76        return $this->dir;
77    }
78
79    /**
80     * Save debug data
81     *
82     * @param  string $id
83     * @param  mixed  $value
84     * @return void
85     */
86    public function save(string $id, mixed $value): void
87    {
88        $filename = $id;
89
90        if ($this->format == self::JSON) {
91            $filename .= '.json';
92        } else if ($this->format == self::PHP) {
93            $filename .= '.php';
94        } else {
95            $filename .= '.log';
96        }
97
98        file_put_contents($this->dir . DIRECTORY_SEPARATOR . $filename, $this->encodeValue($value));
99    }
100
101    /**
102     * Get debug data by ID
103     *
104     * @param  string $id
105     * @return mixed
106     */
107    public function getById(string $id): mixed
108    {
109        if (str_ends_with($id, '*') || str_ends_with($id, '%')) {
110            $id = substr($id, 0, -1);
111            return array_values(array_filter(scandir($this->dir), function($value) use ($id) {
112                return (($value != '.') && ($value != '..')) && str_starts_with($value, $id);
113            }));
114        } else {
115            return $this->decodeValue($this->dir . DIRECTORY_SEPARATOR . $id);
116        }
117    }
118
119    /**
120     * Get debug data by type
121     *
122     * @param  string $type
123     * @return mixed
124     */
125    public function getByType(string $type): mixed
126    {
127        return array_values(array_filter(scandir($this->dir), function($value) use ($type) {
128            return (($value != '.') && ($value != '..')) && str_contains($value, $type);
129        }));
130    }
131
132    /**
133     * Determine if debug data exists by ID
134     *
135     * @param  string $id
136     * @return bool
137     */
138    public function has(string $id): bool
139    {
140        $fileId = $this->dir . DIRECTORY_SEPARATOR . $id;
141
142        if ($this->format == self::JSON) {
143            if (!str_ends_with($fileId, '.json')) {
144                $fileId .= '.json';
145            }
146        } else if ($this->format == self::PHP) {
147            if (!str_ends_with($fileId, '.php')) {
148                $fileId .= '.php';
149            }
150        } else {
151            if (!str_ends_with($fileId, '.log')) {
152                $fileId .= '.log';
153            }
154        }
155
156        return (file_exists($fileId));
157    }
158
159    /**
160     * Delete debug data by ID
161     *
162     * @param  string $id
163     * @return void
164     */
165    public function delete(string $id): void
166    {
167        $fileId = $this->dir . DIRECTORY_SEPARATOR . $id;
168
169        if ($this->format == self::JSON) {
170            if (!str_ends_with($fileId, '.json')) {
171                $fileId .= '.json';
172            }
173        } else if ($this->format == self::PHP) {
174            if (!str_ends_with($fileId, '.php')) {
175                $fileId .= '.php';
176            }
177        } else {
178            if (!str_ends_with($fileId, '.log')) {
179                $fileId .= '.log';
180            }
181        }
182
183        if (file_exists($fileId)) {
184            unlink($fileId);
185        }
186    }
187
188    /**
189     * Clear all debug data
190     *
191     * @return void
192     */
193    public function clear(): void
194    {
195        if (!$dh = @opendir($this->dir)) {
196            return;
197        }
198
199        while (false !== ($obj = readdir($dh))) {
200            if (($obj != '.') && ($obj != '..') &&
201                !is_dir($this->dir . DIRECTORY_SEPARATOR . $obj) && is_file($this->dir . DIRECTORY_SEPARATOR . $obj)) {
202                unlink($this->dir . DIRECTORY_SEPARATOR . $obj);
203            }
204        }
205
206        closedir($dh);
207    }
208
209    /**
210     * Encode the value based on the format
211     *
212     * @param  mixed  $value
213     * @throws Exception
214     * @return string
215     */
216    public function encodeValue(mixed $value): string
217    {
218        if ($this->format == self::JSON) {
219            $value = json_encode($value, JSON_PRETTY_PRINT);
220        } else if ($this->format == self::PHP) {
221            $value = "<?php" . PHP_EOL . "return unserialize(base64_decode('" .
222                base64_encode(serialize($value)) . "'));" . PHP_EOL;
223        } else if (!is_string($value)) {
224            throw new Exception('Error: The value must be a string if storing in text format.');
225        }
226
227        return $value;
228    }
229
230    /**
231     * Decode the value based on the format
232     *
233     * @param  mixed  $value
234     * @return mixed
235     */
236    public function decodeValue(mixed $value): mixed
237    {
238        if ($this->format == self::JSON) {
239            if (!str_ends_with($value, '.json')) {
240                $value .= '.json';
241            }
242            $value  = (file_exists($value)) ? json_decode(file_get_contents($value), true) : false;
243        } else if ($this->format == self::PHP) {
244            if (!str_ends_with($value, '.php')) {
245                $value .= '.php';
246            }
247            $value  = (file_exists($value)) ? include $value : false;
248        } else {
249            if (!str_ends_with($value, '.log')) {
250                $value .= '.log';
251            }
252            $value  = (file_exists($value)) ? file_get_contents($value) : false;
253        }
254
255        return $value;
256    }
257
258}