root/framework/trunk/src/main/php/net/stubbles/rdbms/persistence/serializer/stubDatabaseSerializer.php @ 1763

Revision 1763, 14.0 kB (checked in by mikey, 2 years ago)

enable id keyword

  • Property svn:keywords set to Id
Line 
1<?php
2/**
3 * Serializer to store objects in database tables.
4 *
5 * @author      Frank Kleine <mikey@stubbles.net>
6 * @package     stubbles
7 * @subpackage  rdbms_persistence_serializer
8 */
9stubClassLoader::load('net::stubbles::rdbms::stubDatabaseConnection',
10                      'net::stubbles::rdbms::criteria::stubEqualCriterion',
11                      'net::stubbles::rdbms::persistence::stubPersistenceHelper',
12                      'net::stubbles::rdbms::persistence::stubSetterMethodHelper',
13                      'net::stubbles::rdbms::persistence::serializer::stubDatabaseSerializerException',
14                      'net::stubbles::rdbms::querybuilder::stubDatabaseQueryBuilderFactory',
15                      'net::stubbles::rdbms::querybuilder::stubDatabaseTableRow'
16);
17/**
18 * Serializer to store objects in database tables.
19 *
20 * @package     stubbles
21 * @subpackage  rdbms_persistence_serializer
22 */
23class stubDatabaseSerializer extends stubPersistenceHelper
24{
25    /**
26     * list of serializer instances
27     *
28     * @var  array<string,stubDatabaseSerializer>
29     */
30    protected static $instances = array();
31    /**
32     * the connection to use for making the object persistent
33     *
34     * @var  stubDatabaseConnection
35     */
36    protected $connection;
37    /**
38     * data has been inserted
39     */
40    const INSERT                = 'insert';
41    /**
42     * data has been updated
43     */
44    const UPDATE                = 'update';
45
46    /**
47     * constructor
48     *
49     * @param  stubDatabaseConnection    $connection
50     */
51    protected final function __construct(stubDatabaseConnection $connection)
52    {
53        $this->connection = $connection;
54    }
55
56    /**
57     * method to return instances of the finder depending of the connection
58     *
59     * Because the finder itself is stateless and only bound to the connection
60     * this factory methods prevents that a finder for a specific connection is
61     * created more than once.
62     *
63     * @param   stubDatabaseConnection  $connection  connection to use for finding the data
64     * @param   bool                    $refresh     optional  set to true to recreate the instance
65     * @return  stubDatabaseFinder
66     */
67    public static function getInstance(stubDatabaseConnection $connection, $refresh = false)
68    {
69        if (isset(self::$instances[$connection->hashCode()]) === false || true === $refresh) {
70            self::$instances[$connection->hashCode()] = new self($connection);
71        }
72       
73        return self::$instances[$connection->hashCode()];
74    }
75
76    /**
77     * cloning is forbidden
78     *
79     * @throws  stubDatabaseFinderException
80     */
81    protected final function __clone()
82    {
83        throw new stubDatabaseFinderException('Cloning ' . $this->getClassName() . ' is not allowed.');
84    }
85
86    /**
87     * takes an entity and inserts it into the database
88     *
89     * @param   object                           $entity
90     * @return  string
91     * @throws  stubIllegalArgumentException
92     * @throws  stubDatabaseSerializerException
93     * @throws  stubPersistenceException
94     */
95    public function insert($entity)
96    {
97        if (is_object($entity) === false) {
98            throw new stubIllegalArgumentException('Can only serialize objects.');
99        }
100       
101        $entityClass = (($entity instanceof stubObject) ? ($entity->getClass()) : (new stubReflectionObject($entity)));
102        if ($entityClass->hasAnnotation('Entity') === false) {
103            throw new stubPersistenceException('Class ' . $entityClass->getFullQualifiedClassName() . ' is not an entity.');
104        }
105       
106        $stuff = $this->processEntity($entityClass, $entity, self::INSERT);
107        try {
108            $this->processInsertQueries($this->getInsertQuery($stuff['tableRow'], $entity, $stuff['defaultValues']), $entity, array_shift($stuff['primaryKeys']));
109        } catch (stubDatabaseException $dbe) {
110            throw new stubDatabaseSerializerException('Can not persist ' . $entityClass->getFullQualifiedClassName() . ': a database error occured.', $dbe);
111        }
112       
113        return self::INSERT;
114    }
115
116    /**
117     * takes an entity and updates its database entry
118     *
119     * @param   object                           $entity
120     * @return  string
121     * @throws  stubIllegalArgumentException
122     * @throws  stubDatabaseSerializerException
123     * @throws  stubPersistenceException
124     */
125    public function update($entity)
126    {
127        if (is_object($entity) === false) {
128            throw new stubIllegalArgumentException('Can only serialize objects.');
129        }
130       
131        $entityClass = (($entity instanceof stubObject) ? ($entity->getClass()) : (new stubReflectionObject($entity)));
132        if ($entityClass->hasAnnotation('Entity') === false) {
133            throw new stubPersistenceException('Class ' . $entityClass->getFullQualifiedClassName() . ' is not an entity.');
134        }
135       
136        $stuff = $this->processEntity($entityClass, $entity, self::UPDATE);
137        try {
138            $this->processUpdateQueries($this->getUpdateQuery($stuff['tableRow'], $stuff['defaultValues']));
139        } catch (stubDatabaseException $dbe) {
140            throw new stubDatabaseSerializerException('Can not persist ' . $entityClass->getFullQualifiedClassName() . ': a database error occured.', $dbe);
141        }
142           
143        return self::UPDATE;
144    }
145
146    /**
147     * takes an entity and serializes it into the database
148     *
149     * @param   object                           $entity
150     * @return  string
151     * @throws  stubIllegalArgumentException
152     * @throws  stubDatabaseSerializerException
153     * @throws  stubPersistenceException
154     */
155    public function serialize($entity)
156    {
157        if (is_object($entity) === false) {
158            throw new stubIllegalArgumentException('Can only serialize objects.');
159        }
160       
161        $entityClass = (($entity instanceof stubObject) ? ($entity->getClass()) : (new stubReflectionObject($entity)));
162        if ($entityClass->hasAnnotation('Entity') === false) {
163            throw new stubPersistenceException('Class ' . $entityClass->getFullQualifiedClassName() . ' is not an entity.');
164        }
165       
166        $stuff = $this->processEntity($entityClass, $entity);
167        if (count($stuff['primaryKeys']) > 1) {
168            throw new stubDatabaseSerializerException('Persistence error for ' . $entityClass->getFullQualifiedClassName() . ': only one primary key can be null, but at least two primary keys are null: ' . join(', ', array_keys($stuff['primaryKeys'])));
169        }
170       
171        if ($stuff['tableRow']->hasCriterion() === true) {
172            try {
173                $this->processUpdateQueries($this->getUpdateQuery($stuff['tableRow'], $stuff['defaultValues']));
174            } catch (stubDatabaseException $dbe) {
175                throw new stubDatabaseSerializerException('Can not persist ' . $entityClass->getFullQualifiedClassName() . ': a database error occured.', $dbe);
176            }
177           
178            return self::UPDATE;
179        }
180       
181        try {
182            $this->processInsertQueries($this->getInsertQuery($stuff['tableRow'], $entity, $stuff['defaultValues']), $entity, array_shift($stuff['primaryKeys']));
183        } catch (stubDatabaseException $dbe) {
184            throw new stubDatabaseSerializerException('Can not persist ' . $entityClass->getFullQualifiedClassName() . ': a database error occured.', $dbe);
185        }
186       
187        return self::INSERT;
188    }
189
190    /**
191     * processes the entity: create another presentation of data
192     *
193     * @param   stubBaseReflectionClass  $entityClass
194     * @param   object                   $entity
195     * @param   string                   $type         optional
196     * @return  array
197     * @throws  stubDatabaseSerializerException
198     */
199    protected function processEntity(stubBaseReflectionClass $entityClass, $entity, $type = null)
200    {
201        $tableRow      = new stubDatabaseTableRow($this->getTableDescription($entityClass)->getName());
202        $methods       = $entityClass->getMethods();
203        $primaryKeys   = array();
204        $defaultValues = array();
205        foreach ($methods as $method) {
206            $column = $this->getTableColumn($method);
207            if (null === $column) {
208                continue;
209            }
210           
211            try {
212                $value = $method->invoke($entity);
213            } catch (ReflectionException $re) {
214                throw new stubDatabaseSerializerException('Can not get return value of ' . $entityClass->getFullQualifiedClassName() . '::' . $method->getName() . '(), invocation failed.', $re);
215            }
216           
217            if ($column->isPrimaryKey() === true) {
218                if (null === $value && self::UPDATE === $type) {
219                    throw new stubDatabaseSerializerException('Persistence error for ' . $entityClass->getFullQualifiedClassName() . ': should be updated, but one primary key column is null: ' . $method->getName());
220                } elseif (null === $value) {
221                    $primaryKeys[$method->getName()] = array('setterMethod' => stubSetterMethodHelper::create($column, $entityClass, $method->getName()),
222                                                             'tableName'    => $tableRow->getTableName()
223                                                       );
224                } elseif (self::INSERT === $type) {
225                    $tableRow->setColumn($column->getName(), $value);
226                } else {
227                    $tableRow->addCriterion(new stubEqualCriterion($column->getName(), $value, $tableRow->getTableName()));
228                }
229            } elseif (null === $value) {
230                $defaultValue = $column->getDefaultValue();
231                if ($column->isNullable() === false && null === $defaultValue) {
232                    throw new stubDatabaseSerializerException('Persistence error for ' . $entityClass->getFullQualifiedClassName() . ': column ' . $column->getName() . ' is not allowed to be null but return value from method ' . $method->getName() . ' and default value are both null.');
233                }
234               
235                $defaultValues[] = array('setterMethod' => stubSetterMethodHelper::create($column, $entityClass, $method->getName()),
236                                         'value'        => $value,
237                                         'defaultValue' => $defaultValue,
238                                         'column'       => $column->getName()
239                                   );
240            } else {
241                $tableRow->setColumn($column->getName(), $value);
242            }
243        }
244       
245        return array('tableRow'      => $tableRow,
246                     'defaultValues' => $defaultValues,
247                     'primaryKeys'   => $primaryKeys
248               );
249    }
250
251    /**
252     * creates the queries required to process the insert
253     *
254     * @param   stubDatabaseTableRow  $tableRow
255     * @param   object                $entity
256     * @param   array                 $defaultValues
257     * @return  array<string>
258     * @throws  stubDatabaseSerializerException
259     */
260    protected function getInsertQuery(stubDatabaseTableRow $tableRow, $entity, array $defaultValues)
261    {
262        $queryBuilder = stubDatabaseQueryBuilderFactory::create($this->connection);
263        try {
264            // fill default values into entity and table row
265            foreach ($defaultValues as $defaultValue) {
266                $defaultValue['setterMethod']->invokeArgs($entity, array($defaultValue['defaultValue']));
267                $tableRow->setColumn($defaultValue['column'], $defaultValue['defaultValue']);
268            }
269
270            return $queryBuilder->createInsert(array($tableRow->getTableName() => $tableRow));
271        } catch (stubIllegalArgumentException $iae) {
272            throw new stubDatabaseSerializerException('Creating the queries failed.', $iae);
273        }
274    }
275
276    /**
277     * creates the queries required to process the update
278     *
279     * @param   stubDatabaseTableRow  $tableRow
280     * @param   array                 $defaultValues
281     * @return  array<string>
282     * @throws  stubDatabaseSerializerException
283     */
284    protected function getUpdateQuery(stubDatabaseTableRow $tableRow, array $defaultValues)
285    {
286        $queryBuilder = stubDatabaseQueryBuilderFactory::create($this->connection);
287        try {
288            foreach ($defaultValues as $defaultValue) {
289                $tableRow->setColumn($defaultValue['column'], $defaultValue['value']);
290            }
291           
292            return $queryBuilder->createUpdate(array($tableRow->getTableName() => $tableRow));
293        } catch (stubIllegalArgumentException $iae) {
294            throw new stubDatabaseSerializerException('Creating the queries failed.', $iae);
295        }
296    }
297
298    /**
299     * process insert queries
300     *
301     * @param   array<string,string>   $queries           list of queries to process
302     * @param   object                 $entity            the entity to process the queries for
303     * @param   array<string,string>   $singlePrimaryKey  optional  information about the single primary key
304     * @throws  stubDatabaseException
305     */
306    protected function processInsertQueries(array $queries, $entity, array $singlePrimaryKey = null)
307    {
308        foreach ($queries as $tableName => $query) {
309            $this->connection->exec($query);
310            if (null !== $singlePrimaryKey && $singlePrimaryKey['tableName'] == $tableName) {
311                $singlePrimaryKey['setterMethod']->invokeArgs($entity, array($this->connection->getLastInsertId()));
312            }
313        }
314    }
315
316    /**
317     * process update queries
318     *
319     * @param   array<string,string>   $queries           list of queries to process
320     * @throws  stubDatabaseException
321     */
322    protected function processUpdateQueries(array $queries)
323    {
324        foreach ($queries as $tableName => $query) {
325            $this->connection->exec($query);
326        }
327    }
328
329    /**
330     * returns a unique hash code for the class
331     *
332     * Two serializers are equal if they use the same connection.
333     *
334     * @return  string
335     */
336    public function hashCode()
337    {
338        return 'serializer:' . $this->connection->hashCode();
339    }
340}
341?>
Note: See TracBrowser for help on using the browser.