Skip to content

Commit 04a3044

Browse files
authored
Merge pull request #23 from codeigniter4/array-handler
Array Handler
2 parents 5beef4c + b9c141d commit 04a3044

File tree

7 files changed

+431
-364
lines changed

7 files changed

+431
-364
lines changed

src/Config/Settings.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace CodeIgniter\Settings\Config;
44

5+
use CodeIgniter\Settings\Handlers\ArrayHandler;
56
use CodeIgniter\Settings\Handlers\DatabaseHandler;
67

78
class Settings
@@ -15,6 +16,14 @@ class Settings
1516
*/
1617
public $handlers = ['database'];
1718

19+
/**
20+
* Array handler settings.
21+
*/
22+
public $array = [
23+
'class' => ArrayHandler::class,
24+
'writeable' => true,
25+
];
26+
1827
/**
1928
* Database handler settings.
2029
*/

src/Handlers/ArrayHandler.php

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
namespace CodeIgniter\Settings\Handlers;
4+
5+
/**
6+
* Array Settings Handler
7+
*
8+
* Uses local storage to handle non-persistent
9+
* Settings requests. Useful mostly for testing
10+
* or extension by true persistent handlers.
11+
*/
12+
class ArrayHandler extends BaseHandler
13+
{
14+
/**
15+
* Storage for general settings.
16+
* Format: ['class' => ['property' => ['value', 'type']]]
17+
*
18+
* @var array<string,array<string,array>>
19+
*/
20+
private $general = [];
21+
22+
/**
23+
* Storage for context settings.
24+
* Format: ['context' => ['class' => ['property' => ['value', 'type']]]]
25+
*
26+
* @var array<string,array|null>
27+
*/
28+
private $contexts = [];
29+
30+
public function has(string $class, string $property, ?string $context = null): bool
31+
{
32+
return $this->hasStored($class, $property, $context);
33+
}
34+
35+
public function get(string $class, string $property, ?string $context = null)
36+
{
37+
return $this->getStored($class, $property, $context);
38+
}
39+
40+
public function set(string $class, string $property, $value = null, ?string $context = null)
41+
{
42+
$this->setStored($class, $property, $value, $context);
43+
}
44+
45+
public function forget(string $class, string $property, ?string $context = null)
46+
{
47+
$this->forgetStored($class, $property, $context);
48+
}
49+
50+
/**
51+
* Checks whether this value is in storage.
52+
*/
53+
protected function hasStored(string $class, string $property, ?string $context): bool
54+
{
55+
if ($context === null) {
56+
return isset($this->general[$class])
57+
? array_key_exists($property, $this->general[$class])
58+
: false;
59+
}
60+
61+
return isset($this->contexts[$context][$class])
62+
? array_key_exists($property, $this->contexts[$context][$class])
63+
: false;
64+
}
65+
66+
/**
67+
* Retrieves a value from storage.
68+
*
69+
* @return mixed|null
70+
*/
71+
protected function getStored(string $class, string $property, ?string $context)
72+
{
73+
if (! $this->has($class, $property, $context)) {
74+
return null;
75+
}
76+
77+
return $context === null
78+
? $this->parseValue(...$this->general[$class][$property])
79+
: $this->parseValue(...$this->contexts[$context][$class][$property]);
80+
}
81+
82+
/**
83+
* Adds values to storage.
84+
*
85+
* @param mixed $value
86+
*/
87+
protected function setStored(string $class, string $property, $value, ?string $context): void
88+
{
89+
$type = gettype($value);
90+
$value = $this->prepareValue($value);
91+
92+
if ($context === null) {
93+
$this->general[$class][$property] = [
94+
$value,
95+
$type,
96+
];
97+
} else {
98+
$this->contexts[$context][$class][$property] = [
99+
$value,
100+
$type,
101+
];
102+
}
103+
}
104+
105+
/**
106+
* Deletes an item from storage.
107+
*/
108+
protected function forgetStored(string $class, string $property, ?string $context): void
109+
{
110+
if ($context === null) {
111+
unset($this->general[$class][$property]);
112+
} else {
113+
unset($this->contexts[$context][$class][$property]);
114+
}
115+
}
116+
}

src/Handlers/DatabaseHandler.php

Lines changed: 31 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
use RuntimeException;
77

88
/**
9-
* Provides database storage for Settings.
10-
* Uses local storage to minimize database calls.
9+
* Provides database persistence for Settings.
10+
* Uses ArrayHandler for storage to minimize database calls.
1111
*/
12-
class DatabaseHandler extends BaseHandler
12+
class DatabaseHandler extends ArrayHandler
1313
{
1414
/**
1515
* The database table to use.
@@ -19,20 +19,11 @@ class DatabaseHandler extends BaseHandler
1919
private $table;
2020

2121
/**
22-
* Storage for cached general settings.
23-
* Format: ['class' => ['property' => ['value', 'type']]]
22+
* Array of contexts that have been stored.
2423
*
25-
* @var array<string,array<string,array>>|null Will be null until hydrated
24+
* @var ?string[]
2625
*/
27-
private $general;
28-
29-
/**
30-
* Storage for cached context settings.
31-
* Format: ['context' => ['class' => ['property' => ['value', 'type']]]]
32-
*
33-
* @var array<string,array|null>
34-
*/
35-
private $contexts = [];
26+
private $hydrated = [];
3627

3728
/**
3829
* Stores the configured database table.
@@ -49,15 +40,7 @@ public function has(string $class, string $property, ?string $context = null): b
4940
{
5041
$this->hydrate($context);
5142

52-
if ($context === null) {
53-
return isset($this->general[$class])
54-
? array_key_exists($property, $this->general[$class])
55-
: false;
56-
}
57-
58-
return isset($this->contexts[$context][$class])
59-
? array_key_exists($property, $this->contexts[$context][$class])
60-
: false;
43+
return $this->hasStored($class, $property, $context);
6144
}
6245

6346
/**
@@ -70,13 +53,7 @@ public function has(string $class, string $property, ?string $context = null): b
7053
*/
7154
public function get(string $class, string $property, ?string $context = null)
7255
{
73-
if (! $this->has($class, $property, $context)) {
74-
return null;
75-
}
76-
77-
return $context === null
78-
? $this->parseValue(...$this->general[$class][$property])
79-
: $this->parseValue(...$this->contexts[$context][$class][$property]);
56+
return $this->getStored($class, $property, $context);
8057
}
8158

8259
/**
@@ -90,9 +67,9 @@ public function get(string $class, string $property, ?string $context = null)
9067
*/
9168
public function set(string $class, string $property, $value = null, ?string $context = null)
9269
{
93-
$time = Time::now()->format('Y-m-d H:i:s');
94-
$type = gettype($value);
95-
$value = $this->prepareValue($value);
70+
$time = Time::now()->format('Y-m-d H:i:s');
71+
$type = gettype($value);
72+
$prepared = $this->prepareValue($value);
9673

9774
// If it was stored then we need to update
9875
if ($this->has($class, $property, $context)) {
@@ -101,7 +78,7 @@ public function set(string $class, string $property, $value = null, ?string $con
10178
->where('key', $property)
10279
->where('context', $context)
10380
->update([
104-
'value' => $value,
81+
'value' => $prepared,
10582
'type' => $type,
10683
'context' => $context,
10784
'updated_at' => $time,
@@ -112,31 +89,21 @@ public function set(string $class, string $property, $value = null, ?string $con
11289
->insert([
11390
'class' => $class,
11491
'key' => $property,
115-
'value' => $value,
92+
'value' => $prepared,
11693
'type' => $type,
11794
'context' => $context,
11895
'created_at' => $time,
11996
'updated_at' => $time,
12097
]);
12198
}
12299

123-
// Update storage
124-
if ($result === true) {
125-
if ($context === null) {
126-
$this->general[$class][$property] = [
127-
$value,
128-
$type,
129-
];
130-
} else {
131-
$this->contexts[$context][$class][$property] = [
132-
$value,
133-
$type,
134-
];
135-
}
136-
} else {
100+
if ($result !== true) {
137101
throw new RuntimeException(db_connect()->error()['message'] ?? 'Error writing to the database.');
138102
}
139103

104+
// Update storage
105+
$this->setStored($class, $property, $value, $context);
106+
140107
return $result;
141108
}
142109

@@ -160,64 +127,47 @@ public function forget(string $class, string $property, ?string $context = null)
160127
}
161128

162129
// Delete from local storage
163-
if ($context === null) {
164-
unset($this->general[$class][$property]);
165-
} else {
166-
unset($this->contexts[$context][$class][$property]);
167-
}
130+
$this->forgetStored($class, $property, $context);
168131

169132
return $result;
170133
}
171134

172135
/**
173136
* Fetches values from the database in bulk to minimize calls.
174-
* General is always fetched once, contexts are fetched in their
175-
* entirety for each new request.
137+
* General (null) is always fetched once, contexts are fetched
138+
* in their entirety for each new request.
176139
*
177140
* @throws RuntimeException For database failures
178141
*/
179142
private function hydrate(?string $context)
180143
{
144+
// Check for completion
145+
if (in_array($context, $this->hydrated, true)) {
146+
return;
147+
}
148+
181149
if ($context === null) {
182-
// Check for completion
183-
if ($this->general !== null) {
184-
return;
185-
}
150+
$this->hydrated[] = null;
186151

187-
$this->general = [];
188-
$query = db_connect()->table($this->table)->where('context', null);
152+
$query = db_connect()->table($this->table)->where('context', null);
189153
} else {
190-
// Check for completion
191-
if (isset($this->contexts[$context])) {
192-
return;
193-
}
194-
195154
$query = db_connect()->table($this->table)->where('context', $context);
196155

197156
// If general has not been hydrated we will do that at the same time
198-
if ($this->general === null) {
199-
$this->general = [];
157+
if (! in_array(null, $this->hydrated, true)) {
158+
$this->hydrated[] = null;
200159
$query->orWhere('context', null);
201160
}
202161

203-
$this->contexts[$context] = [];
162+
$this->hydrated[] = $context;
204163
}
205164

206165
if (is_bool($result = $query->get())) {
207166
throw new RuntimeException(db_connect()->error()['message'] ?? 'Error reading from database.');
208167
}
209168

210169
foreach ($result->getResultObject() as $row) {
211-
$tuple = [
212-
$row->value,
213-
$row->type,
214-
];
215-
216-
if ($row->context === null) {
217-
$this->general[$row->class][$row->key] = $tuple;
218-
} else {
219-
$this->contexts[$row->context][$row->class][$row->key] = $tuple;
220-
}
170+
$this->setStored($row->class, $row->key, $this->parseValue($row->value, $row->type), $row->context);
221171
}
222172
}
223173
}

0 commit comments

Comments
 (0)