1 | <?php |
||
19 | class BasicDbEntityService |
||
20 | { |
||
21 | /** |
||
22 | * The database adapter/connection/handler. |
||
23 | * |
||
24 | * @var Db |
||
25 | */ |
||
26 | protected $db; |
||
27 | |||
28 | /** |
||
29 | * Constructor. |
||
30 | * |
||
31 | * @param Db $db |
||
32 | */ |
||
33 | 15 | public function __construct(Db $db) |
|
37 | |||
38 | /** |
||
39 | * Load object's values from database table. |
||
40 | * |
||
41 | * @param AbstractDbEntity $dbEntity |
||
42 | * @throws Exception\EntityNotFoundException |
||
43 | */ |
||
44 | 3 | public function load(AbstractDbEntity $dbEntity) |
|
45 | { |
||
46 | // Check that the primary key is set |
||
47 | 3 | if (!$dbEntity->getPrimaryDbValue()) { |
|
48 | 1 | throw new \InvalidArgumentException( |
|
49 | 1 | 'Database entity can not be loaded because primary value is not set' |
|
50 | ); |
||
51 | } |
||
52 | |||
53 | // Fetch ad from db |
||
54 | 2 | $row = $this->db->fetchRow( |
|
55 | ' |
||
56 | SELECT * |
||
57 | 2 | FROM `' . $dbEntity->getDbTableName() . '` |
|
58 | 2 | WHERE ' . $this->getPrimaryKeyWhereSql($dbEntity) . ' |
|
59 | ', |
||
60 | 2 | $this->getPrimaryKeyWhereParameters($dbEntity) |
|
61 | ); |
||
62 | |||
63 | 2 | if (!$row) { |
|
64 | 1 | throw new Exception\EntityNotFoundException("Db entity[{$dbEntity->getPrimaryDbValue()}] does not exist"); |
|
65 | } |
||
66 | |||
67 | 1 | $dbEntity->setDbDataFromRow($row); |
|
68 | 1 | } |
|
69 | |||
70 | /** |
||
71 | * Save entity to database table. |
||
72 | * |
||
73 | * @param AbstractDbEntity $dbEntity |
||
74 | * @return bool |
||
75 | */ |
||
76 | 10 | public function save(AbstractDbEntity $dbEntity) |
|
77 | { |
||
78 | 10 | if ($dbEntity->shouldBeDeletedFromDbOnSave()) { |
|
79 | // Only delete if previously saved to db |
||
80 | 2 | if ($dbEntity->getPrimaryDbValue()) { |
|
81 | 1 | $this->delete($dbEntity); |
|
82 | } |
||
83 | |||
84 | 2 | return false; |
|
85 | } |
||
86 | |||
87 | 8 | if ($dbEntity->shouldInsertOnDbSave()) { |
|
88 | // Note that database data always contains all properties, with defaults for non set properties |
||
89 | 5 | $dataToSave = $dbEntity->getDbData(); |
|
90 | } else { |
||
91 | 3 | if ($dbEntity->hasModifiedDbProperties()) { |
|
92 | 2 | $dataToSave = $dbEntity->getModifiedDbData(); |
|
93 | } else { |
||
94 | // Return if no value has been modified and it's not an insert |
||
95 | // (we always want to insert if no id exist, since some child objects might |
||
96 | // depend on the this primary id being available) |
||
97 | 1 | return false; |
|
98 | } |
||
99 | } |
||
100 | |||
101 | // We don't the want to insert/update primary value unless forced insert |
||
102 | 7 | if (!$dbEntity->shouldForceDbInsertOnSave()) { |
|
103 | 7 | $primaryKey = $dbEntity->getPrimaryDbPropertyKey(); |
|
104 | 7 | if (is_array($primaryKey)) { |
|
105 | 1 | foreach ($primaryKey as $keyPart) { |
|
106 | 1 | unset($dataToSave[$keyPart]); |
|
107 | } |
||
108 | } else { |
||
109 | 6 | unset($dataToSave[$primaryKey]); |
|
110 | } |
||
111 | } |
||
112 | |||
113 | // Check data |
||
114 | 7 | $sqlData = []; |
|
115 | 7 | foreach ($dataToSave as $propertyName => $value) { |
|
116 | 7 | if (!empty($value) && is_scalar($value) |
|
117 | 7 | && $dbEntity->getDbPropertyMaxLength($propertyName) |
|
118 | 7 | && mb_strlen($value) > $dbEntity->getDbPropertyMaxLength($propertyName) |
|
119 | ) { |
||
120 | 1 | throw new \RuntimeException( |
|
121 | 1 | "Database field \"{$propertyName}\" exceeds field max length (value: \"{$value}\")" |
|
122 | ); |
||
123 | 6 | } elseif (empty($value) && $dbEntity->getDbPropertyNonEmpty($propertyName)) { |
|
124 | 1 | throw new \RuntimeException("Database field \"{$propertyName}\" is empty and required"); |
|
125 | 6 | } elseif (((is_scalar($value) && ((string) $value) === '') || (!is_scalar($value) && empty($value))) |
|
126 | 6 | && $dbEntity->getDbPropertyRequired($propertyName) |
|
127 | ) { |
||
128 | 2 | throw new \RuntimeException("Database field \"{$propertyName}\" is required to be set"); |
|
129 | } |
||
130 | |||
131 | // Set data keys db field format |
||
132 | 4 | $fieldName = $dbEntity->getDbFieldName($propertyName); |
|
133 | 4 | $sqlData[$fieldName] = $value; |
|
134 | } |
||
135 | |||
136 | |||
137 | // Insert new database row |
||
138 | 3 | if ($dbEntity->shouldInsertOnDbSave()) { |
|
139 | 1 | $this->db->insert( |
|
140 | 1 | $dbEntity->getDbTableName(), |
|
141 | 1 | $sqlData |
|
142 | ); |
||
143 | |||
144 | 1 | if (!is_array($dbEntity->getPrimaryDbPropertyKey()) |
|
145 | 1 | && !empty($lastInsertId = $this->db->getLastInsertId()) |
|
146 | ) { |
||
147 | 1 | $dbEntity->setPrimaryDbValue($lastInsertId); |
|
148 | } |
||
149 | // Update existing database row |
||
150 | } else { |
||
151 | 2 | $this->db->update( |
|
152 | 2 | $dbEntity->getDbTableName(), |
|
153 | 2 | $sqlData, |
|
154 | 2 | $this->getPrimaryKeyWhereSql($dbEntity), |
|
155 | 2 | $this->getPrimaryKeyWhereParameters($dbEntity) |
|
156 | |||
157 | ); |
||
158 | } |
||
159 | |||
160 | 3 | $dbEntity->clearModifiedDbProperties(); |
|
161 | 3 | $dbEntity->setForceDbInsertOnSave(false); |
|
162 | |||
163 | 3 | return true; |
|
164 | } |
||
165 | |||
166 | /** |
||
167 | * Delete entity's corresponding database row. |
||
168 | * |
||
169 | * @param AbstractDbEntity $dbEntity |
||
170 | */ |
||
171 | 3 | public function delete(AbstractDbEntity $dbEntity) |
|
184 | |||
185 | /** |
||
186 | * @param AbstractDbEntity $dbEntity |
||
187 | * @return string |
||
188 | */ |
||
189 | 6 | protected function getPrimaryKeyWhereSql(AbstractDbEntity $dbEntity) |
|
203 | |||
204 | /** |
||
205 | * @param AbstractDbEntity $dbEntity |
||
206 | * @return array |
||
207 | */ |
||
208 | 6 | protected function getPrimaryKeyWhereParameters(AbstractDbEntity $dbEntity) |
|
218 | } |
||
219 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.