Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
37 / 37 |
Entity | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
18 | |
100.00% |
37 / 37 |
__call | |
100.00% |
1 / 1 |
2 | |
100.00% |
6 / 6 |
|||
create | |
100.00% |
1 / 1 |
6 | |
100.00% |
13 / 13 |
|||
createOrUpdate | |
100.00% |
1 / 1 |
4 | |
100.00% |
7 / 7 |
|||
getWithPK | |
100.00% |
1 / 1 |
5 | |
100.00% |
11 / 11 |
|||
jsonSerialize | n/a |
0 / 0 |
1 | n/a |
0 / 0 |
1 | <?php |
2 | |
3 | // {{{ License |
4 | |
5 | // This file is part of GNU social - https://www.gnu.org/software/social |
6 | // |
7 | // GNU social is free software: you can redistribute it and/or modify |
8 | // it under the terms of the GNU Affero General Public License as published by |
9 | // the Free Software Foundation, either version 3 of the License, or |
10 | // (at your option) any later version. |
11 | // |
12 | // GNU social is distributed in the hope that it will be useful, |
13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | // GNU Affero General Public License for more details. |
16 | // |
17 | // You should have received a copy of the GNU Affero General Public License |
18 | // along with GNU social. If not, see <http://www.gnu.org/licenses/>. |
19 | |
20 | // }}} |
21 | |
22 | namespace App\Core; |
23 | |
24 | use App\Core\DB\DB; |
25 | use App\Util\Exception\NotFoundException; |
26 | use App\Util\Exception\ServerException; |
27 | use App\Util\Formatting; |
28 | use DateTime; |
29 | |
30 | /** |
31 | * Base class to all entities, with some utilities |
32 | */ |
33 | abstract class Entity implements \JsonSerializable |
34 | { |
35 | public function __call(string $name , array $arguments): mixed |
36 | { |
37 | if (Formatting::startsWith($name, 'has')) { |
38 | $prop = Formatting::camelCaseToSnakeCase(Formatting::removePrefix($name, 'has')); |
39 | // https://wiki.php.net/rfc/closure_apply#proposal |
40 | $private_property_accessor = function ($prop) { return isset($this->{$prop}); }; |
41 | $private_property_accessor = $private_property_accessor->bindTo($this, get_called_class()); |
42 | return $private_property_accessor($prop); |
43 | } |
44 | throw new \BadMethodCallException('Non existent method ' . get_called_class() . "::{$name} called with arguments: " . print_r($arguments, true)); |
45 | } |
46 | |
47 | /** |
48 | * Create an instance of the called class or fill in the |
49 | * properties of $obj with the associative array $args. Doesn't |
50 | * persist the result |
51 | * |
52 | * @param null|mixed $obj |
53 | */ |
54 | public static function create(array $args, $obj = null) |
55 | { |
56 | $class = get_called_class(); |
57 | $obj = $obj ?: new $class(); |
58 | $date = new DateTime(); |
59 | foreach (['created', 'modified'] as $prop) { |
60 | if (property_exists($class, $prop)) { |
61 | $args[$prop] = $date; |
62 | } |
63 | } |
64 | |
65 | foreach ($args as $prop => $val) { |
66 | if (property_exists($obj, $prop)) { |
67 | $set = 'set' . ucfirst(Formatting::snakeCaseToCamelCase($prop)); |
68 | $obj->{$set}($val); |
69 | } else { |
70 | Log::error($m = "Property {$class}::{$prop} doesn't exist"); |
71 | throw new \InvalidArgumentException($m); |
72 | } |
73 | } |
74 | return $obj; |
75 | } |
76 | |
77 | /** |
78 | * Create a new instance, but check for duplicates |
79 | * |
80 | * @return [$obj, $is_update] |
81 | */ |
82 | public static function createOrUpdate(array $args, array $find_by_keys = []) |
83 | { |
84 | $table = DB::getTableForClass(get_called_class()); |
85 | $find_by = $find_by_keys == [] ? $args : array_intersect_key($args, array_flip($find_by_keys)); |
86 | try { |
87 | $obj = DB::findOneBy($table, $find_by); |
88 | } catch (NotFoundException) { |
89 | $obj = null; |
90 | // @codeCoverageIgnoreStart |
91 | } catch (\Exception $e) { |
92 | Log::unexpected_exception($e); |
93 | // @codeCoverageIgnoreEnd |
94 | } |
95 | $is_update = $obj !== null; |
96 | return [self::create($args, $obj), $is_update]; |
97 | } |
98 | |
99 | /** |
100 | * Get an Entity from its primary key |
101 | * |
102 | * Support multiple formats: |
103 | * - mixed $values - convert to array and check next |
104 | * - array[int => mixed] $values - get keys for entity and set them in order and proceed to next case |
105 | * - array[string => mixed] $values - Perform a regular find |
106 | * |
107 | * Examples: |
108 | * Entity::getWithPK(42); |
109 | * Entity::getWithPK([42, 'foo']); |
110 | * Entity::getWithPK(['key1' => 42, 'key2' => 'foo']) |
111 | * |
112 | * @return null|static |
113 | */ |
114 | public static function getWithPK(mixed $values): ?self |
115 | { |
116 | $values = is_array($values) ? $values : [$values]; |
117 | $class = get_called_class(); |
118 | $keys = DB::getPKForClass($class); |
119 | $find_by = []; |
120 | foreach ($values as $k => $v) { |
121 | if (is_string($k)) { |
122 | $find_by[$k] = $v; |
123 | } else { |
124 | $find_by[$keys[$k]] = $v; |
125 | } |
126 | } |
127 | try { |
128 | return DB::findOneBy($class, $find_by); |
129 | } catch (NotFoundException $e) { |
130 | return null; |
131 | } |
132 | } |
133 | |
134 | /** |
135 | * Called when json_encode encounters this object. Not all |
136 | * entities will need json encoding, so it doesn't make sense to |
137 | * make this abstract |
138 | * |
139 | * @throw ServerException |
140 | * @codeCoverageIgnore |
141 | */ |
142 | public function jsonSerialize() |
143 | { |
144 | throw new ServerException(_m('Unimplemented method')); |
145 | } |
146 | } |