Skip to content

Commit e344086

Browse files
committed
Extract saving and deleting of items in own class
1 parent 8bae9c9 commit e344086

File tree

3 files changed

+397
-218
lines changed

3 files changed

+397
-218
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/**
4+
* This file is part of MetaModels/core.
5+
*
6+
* (c) 2012-2017 The MetaModels team.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* This project is provided in good faith and hope to be usable by anyone.
12+
*
13+
* @package MetaModels
14+
* @subpackage Core
15+
* @author Christian Schiffler <c.schiffler@cyberspectrum.de>
16+
* @copyright 2012-2017 The MetaModels team.
17+
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0
18+
* @filesource
19+
*/
20+
21+
namespace MetaModels\DataAccess;
22+
23+
/**
24+
* This trait provides some helper functions for database access.
25+
*/
26+
trait DatabaseHelperTrait
27+
{
28+
/**
29+
* Build a list of the correct amount of "?" for use in a db query.
30+
*
31+
* @param array $parameters The parameters.
32+
*
33+
* @return string
34+
*/
35+
private function buildDatabaseParameterList(array $parameters)
36+
{
37+
return implode(',', array_fill(0, count($parameters), '?'));
38+
}
39+
}
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
<?php
2+
3+
/**
4+
* This file is part of MetaModels/core.
5+
*
6+
* (c) 2012-2017 The MetaModels team.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* This project is provided in good faith and hope to be usable by anyone.
12+
*
13+
* @package MetaModels
14+
* @subpackage Core
15+
* @author Christian Schiffler <c.schiffler@cyberspectrum.de>
16+
* @copyright 2012-2017 The MetaModels team.
17+
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0
18+
* @filesource
19+
*/
20+
21+
namespace MetaModels\DataAccess;
22+
23+
use Contao\Database;
24+
use MetaModels\Attribute\IAttribute;
25+
use MetaModels\Attribute\IComplex;
26+
use MetaModels\Attribute\ISimple;
27+
use MetaModels\Attribute\ITranslated;
28+
use MetaModels\IItem;
29+
use MetaModels\IMetaModel;
30+
31+
/**
32+
* This class handles the raw database interaction for MetaModels.
33+
*
34+
* @internal Not part of the API.
35+
*/
36+
class ItemPersister
37+
{
38+
use DatabaseHelperTrait;
39+
40+
/**
41+
* The metamodel we work on.
42+
*
43+
* @var IMetaModel
44+
*/
45+
private $metaModel;
46+
47+
/**
48+
* The MetaModel table name.
49+
*
50+
* @var string
51+
*/
52+
private $tableName;
53+
54+
/**
55+
* The used database.
56+
*
57+
* @var Database
58+
*/
59+
private $database;
60+
61+
/**
62+
* Create a new instance.
63+
*
64+
* @param IMetaModel $metaModel The metamodel we work on.
65+
* @param Database $database The used database.
66+
*/
67+
public function __construct(IMetaModel $metaModel, Database $database)
68+
{
69+
$this->metaModel = $metaModel;
70+
$this->tableName = $metaModel->getTableName();
71+
$this->database = $database;
72+
}
73+
74+
/**
75+
* Save an item into the database.
76+
*
77+
* @param IItem $item The item to save to the database.
78+
*
79+
* @return void
80+
*/
81+
public function saveItem(IItem $item)
82+
{
83+
$baseAttributes = false;
84+
$item->set('tstamp', time());
85+
if (null === $item->get('id')) {
86+
$baseAttributes = true;
87+
$this->createNewItem($item);
88+
}
89+
90+
$itemId = $item->get('id');
91+
$data = ['tstamp' => $item->get('tstamp')];
92+
// Update system columns.
93+
if (null !== $item->get('pid')) {
94+
$data['pid'] = $item->get('pid');
95+
}
96+
if (null !== $item->get('sorting')) {
97+
$data['sorting'] = $item->get('sorting');
98+
}
99+
$this->saveRawColumns($data, [$itemId]);
100+
unset($data);
101+
102+
if ($this->metaModel->isTranslated()) {
103+
$language = $this->metaModel->getActiveLanguage();
104+
} else {
105+
$language = null;
106+
}
107+
108+
$variantIds = [];
109+
if ($item->isVariantBase()) {
110+
$variants = $this->metaModel->findVariantsWithBase([$itemId], null);
111+
foreach ($variants as $objVariant) {
112+
/** @var IItem $objVariant */
113+
$variantIds[] = $objVariant->get('id');
114+
}
115+
$this->saveRawColumns(['tstamp' => $item->get('tstamp')], $variantIds);
116+
}
117+
118+
$this->updateVariants($item, $language, $variantIds, $baseAttributes);
119+
120+
// Tell all attributes that the model has been saved. Useful for alias fields, edit counters etc.
121+
foreach ($this->metaModel->getAttributes() as $objAttribute) {
122+
if ($item->isAttributeSet($objAttribute->getColName())) {
123+
$objAttribute->modelSaved($item);
124+
}
125+
}
126+
}
127+
128+
/**
129+
* Remove an item from the database.
130+
*
131+
* @param IItem $item The item to delete from the database.
132+
*
133+
* @return void
134+
*/
135+
public function deleteItem(IItem $item)
136+
{
137+
$idList = [$item->get('id')];
138+
// Determine if the model is a variant base and if so, fetch the variants additionally.
139+
if ($item->isVariantBase()) {
140+
$variants = $this->metaModel->findVariants([$item->get('id')], null);
141+
foreach ($variants as $variant) {
142+
/** @var IItem $variant */
143+
$idList[] = $variant->get('id');
144+
}
145+
}
146+
// Complex and translated attributes shall delete their values first.
147+
$this->deleteAttributeValues($idList);
148+
// Now make the real rows disappear.
149+
$this
150+
->database
151+
->prepare(sprintf(
152+
'DELETE FROM %s WHERE id IN (%s)',
153+
$this->tableName,
154+
$this->buildDatabaseParameterList($idList)
155+
))
156+
->execute($idList);
157+
}
158+
159+
160+
/**
161+
* Create a new item in the database.
162+
*
163+
* @param IItem $item The item to be created.
164+
*
165+
* @return void
166+
*/
167+
private function createNewItem(IItem $item)
168+
{
169+
$data = ['tstamp' => $item->get('tstamp')];
170+
171+
$isNewBaseItem = false;
172+
if ($this->metaModel->hasVariants()) {
173+
// No variant group is given, so we have a complete new base item this should be a workaround for these
174+
// values should be set by the GeneralDataMetaModel or whoever is calling this method.
175+
if (null === $item->get('vargroup')) {
176+
$item->set('varbase', '1');
177+
$item->set('vargroup', '0');
178+
$isNewBaseItem = true;
179+
}
180+
$data['varbase'] = $item->get('varbase');
181+
$data['vargroup'] = $item->get('vargroup');
182+
}
183+
184+
/** @noinspection PhpUndefinedFieldInspection */
185+
$itemId = $this
186+
->database
187+
->prepare('INSERT INTO ' . $this->tableName . ' %s')
188+
->set($data)
189+
->execute()
190+
->insertId;
191+
$item->set('id', $itemId);
192+
193+
// Set the variant group equal to the id.
194+
if ($isNewBaseItem) {
195+
$this->saveRawColumns(['vargroup' => $item->get('id')], [$item->get('id')]);
196+
$item->set('vargroup', $item->get('id'));
197+
}
198+
}
199+
200+
/**
201+
* Update the values of a native columns for the given ids.
202+
*
203+
* @param string[] $columns The column names to update (i.e. tstamp) as key, the values as value.
204+
*
205+
* @param string[] $ids The ids of the items that shall be updated.
206+
*
207+
* @return void
208+
*/
209+
private function saveRawColumns(array $columns, array $ids)
210+
{
211+
$this
212+
->database
213+
->prepare(
214+
sprintf(
215+
'UPDATE %1$s %%s=? WHERE id IN (%3$s)',
216+
$this->tableName,
217+
$this->buildDatabaseParameterList($ids)
218+
)
219+
)
220+
->set($columns)
221+
->execute($ids);
222+
}
223+
224+
/**
225+
* Update the variants with the value if needed.
226+
*
227+
* @param IItem $item The item to save.
228+
* @param string $activeLanguage The language the values are in.
229+
* @param string[] $variantIds The ids of all variants.
230+
* @param bool $baseAttributes If also the base attributes get updated as well.
231+
*
232+
* @return void
233+
*/
234+
private function updateVariants(IItem $item, $activeLanguage, array $variantIds, $baseAttributes)
235+
{
236+
list($variant, $invariant) = $this->splitAttributes($item, $baseAttributes);
237+
238+
// Override in variants.
239+
foreach ($variant as $attributeName => $attribute) {
240+
$this->saveAttributeValues($attribute, $variantIds, $item->get($attributeName), $activeLanguage);
241+
}
242+
// Save invariant ones now.
243+
$ids = [$item->get('id')];
244+
foreach ($invariant as $attributeName => $attribute) {
245+
$this->saveAttributeValues($attribute, $ids, $item->get($attributeName), $activeLanguage);
246+
}
247+
}
248+
249+
/**
250+
* Update an attribute for the given ids with the given data.
251+
*
252+
* @param IAttribute $attribute The attribute to save.
253+
* @param array $ids The ids of the rows that shall be updated.
254+
* @param mixed $data The data to save in raw data.
255+
* @param string $language The language code to save.
256+
*
257+
* @return void
258+
*
259+
* @throws \RuntimeException When an unknown attribute type is encountered.
260+
*/
261+
private function saveAttributeValues($attribute, array $ids, $data, $language)
262+
{
263+
// Call the serializeData for all simple attributes.
264+
if ($attribute instanceof ISimple) {
265+
$data = $attribute->serializeData($data);
266+
}
267+
268+
$arrData = array();
269+
foreach ($ids as $intId) {
270+
$arrData[$intId] = $data;
271+
}
272+
273+
// Check for translated fields first, then for complex and save as simple then.
274+
if ($language && $attribute instanceof ITranslated) {
275+
$attribute->setTranslatedDataFor($arrData, $language);
276+
return;
277+
}
278+
if ($attribute instanceof IComplex) {
279+
$attribute->setDataFor($arrData);
280+
return;
281+
}
282+
if ($attribute instanceof ISimple) {
283+
$attribute->setDataFor($arrData);
284+
return;
285+
}
286+
287+
throw new \RuntimeException(
288+
'Unknown attribute type, can not save. Interfaces implemented: ' .
289+
implode(', ', class_implements($attribute))
290+
);
291+
}
292+
293+
/**
294+
* Delete the values in complex and translated attributes.
295+
*
296+
* @param string[] $idList The list of item ids to remove.
297+
*
298+
* @return void
299+
*/
300+
private function deleteAttributeValues(array $idList)
301+
{
302+
$languages = null;
303+
if ($this->metaModel->isTranslated()) {
304+
$languages = $this->metaModel->getAvailableLanguages();
305+
}
306+
foreach ($this->metaModel->getAttributes() as $attribute) {
307+
if ($attribute instanceof IComplex) {
308+
/** @var IComplex $attribute */
309+
$attribute->unsetDataFor($idList);
310+
continue;
311+
}
312+
if ($attribute instanceof ITranslated) {
313+
foreach ($languages as $language) {
314+
$attribute->unsetValueFor($idList, $language);
315+
}
316+
continue;
317+
}
318+
}
319+
}
320+
321+
/**
322+
* Split the attributes into variant and invariant ones and filter out all that do not need to get updated.
323+
*
324+
* @param IItem $item The item to save.
325+
* @param bool $baseAttributes If also the base attributes get updated as well.
326+
*
327+
* @return array
328+
*/
329+
private function splitAttributes(IItem $item, $baseAttributes)
330+
{
331+
$variant = [];
332+
$invariant = [];
333+
foreach ($this->metaModel->getAttributes() as $attributeName => $attribute) {
334+
// Skip unset attributes.
335+
if (!$item->isAttributeSet($attribute->getColName())) {
336+
continue;
337+
}
338+
if ($this->metaModel->hasVariants()) {
339+
if ($attribute->get('isvariant')) {
340+
$variant[$attributeName] = $attribute;
341+
continue;
342+
}
343+
if (!$baseAttributes && $item->isVariant()) {
344+
// Skip base attribute.
345+
continue;
346+
}
347+
}
348+
$invariant[$attributeName] = $attribute;
349+
}
350+
351+
return [$variant, $invariant];
352+
}
353+
}

0 commit comments

Comments
 (0)