/** * Stores a cleaned bean; i.e. only scalar values. This is the core of the store() * method. When all lists and embedded beans (parent objects) have been processed and * removed from the original bean the bean is passed to this method to be stored * in the database. * * @param OODBBean $bean the clean bean * * @return void */ protected function storeBean(OODBBean $bean) { if ($bean->getMeta('changed')) { list($properties, $table) = $bean->getPropertiesAndType(); $id = $properties['id']; unset($properties['id']); $updateValues = array(); $k1 = 'property'; $k2 = 'value'; foreach ($properties as $key => $value) { $updateValues[] = array($k1 => $key, $k2 => $value); } $bean->id = $this->writer->updateRecord($table, $updateValues, $id); $bean->setMeta('changed', FALSE); } $bean->setMeta('tainted', FALSE); }
/** * {@inheritdoc} */ public function apply(OODBBean $bean, $eventName, array $options) { $fields = $options['events'][$eventName]; $parts = array(); foreach ($fields as $name) { $parts[] = $bean[$name]; } $fieldName = $options['options']['fieldName']; if ($parts) { $bean->setMeta(sprintf('cast.%s', $fieldName), 'string'); $bean->{$fieldName} = $this->sluggify(join(' ', $parts)); } }
/** * Test meta data methods. * * @return void */ public function testMetaData() { testpack('Test meta data'); $bean = new OODBBean(); $bean->setMeta("this.is.a.custom.metaproperty", "yes"); asrt($bean->getMeta("this.is.a.custom.metaproperty"), "yes"); asrt($bean->getMeta("nonexistant"), NULL); asrt($bean->getMeta("nonexistant", "abc"), "abc"); asrt($bean->getMeta("nonexistant.nested"), NULL); asrt($bean->getMeta("nonexistant,nested", "abc"), "abc"); $bean->setMeta("test.two", "second"); asrt($bean->getMeta("test.two"), "second"); $bean->setMeta("another.little.property", "yes"); asrt($bean->getMeta("another.little.property"), "yes"); asrt($bean->getMeta("test.two"), "second"); // Copy Metadata $bean = new OODBBean(); $bean->setMeta("meta.meta", "123"); $bean2 = new OODBBean(); asrt($bean2->getMeta("meta.meta"), NULL); $bean2->copyMetaFrom($bean); asrt($bean2->getMeta("meta.meta"), "123"); }
/** * Normally the check() method is always called indirectly when * dealing with beans. This test ensures we can call check() * directly. Even though frozen repositories do not rely on * bean checking to improve performance the method should still * offer the same functionality when called directly. * * @return void */ public function testCheckDirectly() { $bean = new OODBBean(); $bean->id = 0; $bean->setMeta('type', 'book'); R::getRedBean()->check($bean); $bean->setMeta('type', '.'); try { R::getRedBean()->check($bean); fail(); } catch (\Exception $e) { pass(); } //check should remain the same even if frozen repo is used, method is public after all! //we dont want to break the API! R::freeze(TRUE); try { R::getRedBean()->check($bean); fail(); } catch (\Exception $e) { pass(); } R::freeze(FALSE); }
/** * Associates a pair of beans. This method associates two beans, no matter * what types. Accepts a base bean that contains data for the linking record. * This method is used by associate. This method also accepts a base bean to be used * as the template for the link record in the database. * * @param OODBBean $bean1 first bean * @param OODBBean $bean2 second bean * @param OODBBean $bean base bean (association record) * * @return mixed */ protected function associateBeans(OODBBean $bean1, OODBBean $bean2, OODBBean $bean) { $type = $bean->getMeta('type'); $property1 = $bean1->getMeta('type') . '_id'; $property2 = $bean2->getMeta('type') . '_id'; if ($property1 == $property2) { $property2 = $bean2->getMeta('type') . '2_id'; } $this->oodb->store($bean1); $this->oodb->store($bean2); $bean->setMeta("cast.{$property1}", "id"); $bean->setMeta("cast.{$property2}", "id"); $bean->setMeta('sys.buildcommand.unique', array($property1, $property2)); $bean->{$property1} = $bean1->id; $bean->{$property2} = $bean2->id; $results = array(); try { $id = $this->oodb->store($bean); $results[] = $id; } catch (SQLException $exception) { if (!$this->writer->sqlStateIn($exception->getSQLState(), array(QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION))) { throw $exception; } } return $results; }
/** * Modifies the table to fit the bean data. * Given a property and a value and the bean, this method will * adjust the table structure to fit the requirements of the property and value. * This may include adding a new column or widening an existing column to hold a larger * or different kind of value. This method employs the writer to adjust the table * structure in the database. Schema updates are recorded in meta properties of the bean. * * This method will also apply indexes, unique constraints and foreign keys. * * @param OODBBean $bean bean to get cast data from and store meta in * @param string $property property to store * @param mixed $value value to store * * @return void */ private function modifySchema(OODBBean $bean, $property, $value) { $doFKStuff = FALSE; $table = $bean->getMeta('type'); $columns = $this->writer->getColumns($table); $columnNoQ = $this->writer->esc($property, TRUE); if (!$this->oodb->isChilled($bean->getMeta('type'))) { if ($bean->getMeta("cast.{$property}", -1) !== -1) { //check for explicitly specified types $cast = $bean->getMeta("cast.{$property}"); $typeno = $this->getTypeFromCast($cast); } else { $cast = FALSE; $typeno = $this->writer->scanType($value, TRUE); } if (isset($columns[$this->writer->esc($property, TRUE)])) { //Is this property represented in the table ? if (!$cast) { //rescan without taking into account special types >80 $typeno = $this->writer->scanType($value, FALSE); } $sqlt = $this->writer->code($columns[$this->writer->esc($property, TRUE)]); if ($typeno > $sqlt) { //no, we have to widen the database column type $this->writer->widenColumn($table, $property, $typeno); $bean->setMeta('buildreport.flags.widen', TRUE); $doFKStuff = TRUE; } } else { $this->writer->addColumn($table, $property, $typeno); $bean->setMeta('buildreport.flags.addcolumn', TRUE); $doFKStuff = TRUE; } if ($doFKStuff) { if (strrpos($columnNoQ, '_id') === strlen($columnNoQ) - 3) { $destinationColumnNoQ = substr($columnNoQ, 0, strlen($columnNoQ) - 3); $indexName = "index_foreignkey_{$table}_{$destinationColumnNoQ}"; $this->writer->addIndex($table, $indexName, $columnNoQ); $typeof = $bean->getMeta("sys.typeof.{$destinationColumnNoQ}", $destinationColumnNoQ); $isLink = $bean->getMeta('sys.buildcommand.unique', FALSE); //Make FK CASCADING if part of exclusive list (dependson=typeof) or if link bean $isDep = $bean->moveMeta('sys.buildcommand.fkdependson') === $typeof || is_array($isLink); $result = $this->writer->addFK($table, $typeof, $columnNoQ, 'id', $isDep); //If this is a link bean and all unique columns have been added already, then apply unique constraint if (is_array($isLink) && !count(array_diff($isLink, array_keys($this->writer->getColumns($table))))) { $this->writer->addUniqueConstraint($table, $bean->moveMeta('sys.buildcommand.unique')); $bean->setMeta("sys.typeof.{$destinationColumnNoQ}", NULL); } } } } }
/** * Associates a pair of beans. This method associates two beans, no matter * what types. Accepts a base bean that contains data for the linking record. * This method is used by associate. This method also accepts a base bean to be used * as the template for the link record in the database. * * @param OODBBean $bean1 first bean * @param OODBBean $bean2 second bean * @param OODBBean $bean base bean (association record) * * @throws\Exception|SQL * * @return mixed */ protected function associateBeans(OODBBean $bean1, OODBBean $bean2, OODBBean $bean) { $property1 = $bean1->getMeta('type') . '_id'; $property2 = $bean2->getMeta('type') . '_id'; if ($property1 == $property2) { $property2 = $bean2->getMeta('type') . '2_id'; } //Dont mess with other tables, only add the unique constraint if: //1. the table exists (otherwise we cant inspect it) //2. the table only contains N-M fields: ID, N-ID, M-ID. $unique = array($property1, $property2); $type = $bean->getMeta('type'); $tables = $this->writer->getTables(); if (in_array($type, $tables) && !$this->oodb->isChilled($type)) { $columns = $this->writer->getColumns($type); if (count($columns) === 3 && isset($columns['id']) && isset($columns[$property1]) && isset($columns[$property2])) { $bean->setMeta('buildcommand.unique', array($unique)); } } //add a build command for Single Column Index (to improve performance in case unqiue cant be used) $indexName1 = 'index_for_' . $bean->getMeta('type') . '_' . $property1; $indexName2 = 'index_for_' . $bean->getMeta('type') . '_' . $property2; $bean->setMeta('buildcommand.indexes', array($property1 => $indexName1, $property2 => $indexName2)); $this->oodb->store($bean1); $this->oodb->store($bean2); $bean->setMeta("cast.{$property1}", "id"); $bean->setMeta("cast.{$property2}", "id"); $bean->{$property1} = $bean1->id; $bean->{$property2} = $bean2->id; $results = array(); try { $id = $this->oodb->store($bean); //On creation, add constraints.... if (!$this->oodb->isFrozen() && $bean->getMeta('buildreport.flags.created')) { $bean->setMeta('buildreport.flags.created', 0); if (!$this->oodb->isFrozen()) { $this->writer->addConstraintForTypes($bean1->getMeta('type'), $bean2->getMeta('type')); } } $results[] = $id; } catch (SQL $exception) { if (!$this->writer->sqlStateIn($exception->getSQLState(), array(QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION))) { throw $exception; } } return $results; }
/** * Converts an embedded bean to an ID, removed the bean property and * stores the bean in the embedded beans array. * * @param array $embeddedBeans destination array for embedded bean * @param OODBBean $bean target bean * @param string $property property that contains the embedded bean * @param OODBBean $value embedded bean itself */ protected function processEmbeddedBean(&$embeddedBeans, $bean, $property, OODBBean $value) { $linkField = $property . '_id'; $id = $this->prepareEmbeddedBean($value); if ($bean->{$linkField} != $id) { $bean->{$linkField} = $id; } $bean->setMeta('cast.' . $linkField, 'id'); $embeddedBeans[$linkField] = $value; unset($bean->{$property}); }
/** * ExportAll. * * @return void */ public function testExportAll() { testpack('Test exportAll'); $redbean = R::getRedBean(); $bean = new OODBBean(); $bean->import(array("a" => 1, "b" => 2)); $bean->setMeta("justametaproperty", "hellothere"); $arr = $bean->export(); asrt(is_array($arr), TRUE); asrt(isset($arr["a"]), TRUE); asrt(isset($arr["b"]), TRUE); asrt($arr["a"], 1); asrt($arr["b"], 2); asrt(isset($arr["__info"]), FALSE); $arr = $bean->export(TRUE); asrt(isset($arr["__info"]), TRUE); asrt($arr["a"], 1); asrt($arr["b"], 2); $exportBean = $redbean->dispense("abean"); $exportBean->setMeta("metaitem.bla", 1); $exportedBean = $exportBean->export(TRUE); asrt($exportedBean["__info"]["metaitem.bla"], 1); asrt($exportedBean["__info"]["type"], "abean"); // Can we determine whether a bean is empty? testpack('test $bean->isEmpty() function'); $bean = R::dispense('bean'); asrt($bean->isEmpty(), TRUE); asrt(count($bean) > 0, TRUE); $bean->property = 1; asrt($bean->isEmpty(), FALSE); asrt(count($bean) > 0, TRUE); $bean->property = 0; asrt($bean->isEmpty(), TRUE); asrt(count($bean) > 0, TRUE); $bean->property = FALSE; asrt($bean->isEmpty(), TRUE); asrt(count($bean) > 0, TRUE); $bean->property = NULL; asrt($bean->isEmpty(), TRUE); asrt(count($bean) > 0, TRUE); unset($bean->property); asrt($bean->isEmpty(), TRUE); asrt(count($bean) > 0, TRUE); // Export bug I found $bandmember = R::dispense('bandmember'); $bandmember->name = 'Duke'; $instrument = R::dispense('instrument'); $instrument->name = 'Piano'; $bandmember->ownInstrument[] = $instrument; $a = R::exportAll($bandmember); pass(); asrt(isset($a[0]), TRUE); asrt((int) $a[0]['id'], 0); asrt($a[0]['name'], 'Duke'); asrt($a[0]['ownInstrument'][0]['name'], 'Piano'); R::nuke(); $v = R::dispense('village'); $b = R::dispense('building'); $v->name = 'a'; $b->name = 'b'; $v->ownBuilding[] = $b; $id = R::store($v); $a = R::exportAll($v); asrt($a[0]['name'], 'a'); asrt($a[0]['ownBuilding'][0]['name'], 'b'); $v = R::load('village', $id); $b2 = R::dispense('building'); $b2->name = 'c'; $v->ownBuilding[] = $b2; $a = R::exportAll($v); asrt($a[0]['name'], 'a'); asrt($a[0]['ownBuilding'][0]['name'], 'b'); asrt(count($a[0]['ownBuilding']), 2); list($r1, $r2) = R::dispense('army', 2); $r1->name = '1'; $r2->name = '2'; $v->sharedArmy[] = $r2; $a = R::exportAll($v); asrt(count($a[0]['sharedArmy']), 1); R::store($v); $v = R::load('village', $id); $a = R::exportAll($v); asrt(count($a[0]['sharedArmy']), 1); asrt($a[0]['name'], 'a'); asrt($a[0]['ownBuilding'][0]['name'], 'b'); asrt(count($a[0]['ownBuilding']), 2); $v->sharedArmy[] = $r1; $a = R::exportAll($v); asrt(count($a[0]['sharedArmy']), 2); $v = R::load('village', $id); $a = R::exportAll($v); asrt(count($a[0]['sharedArmy']), 1); $v->sharedArmy[] = $r1; R::store($v); $v = R::load('village', $id); $a = R::exportAll($v); asrt(count($a[0]['sharedArmy']), 2); }
/** * Molds the table to fit the bean data. * Given a property and a value and the bean, this method will * adjust the table structure to fit the requirements of the property and value. * This may include adding a new column or widening an existing column to hold a larger * or different kind of value. This method employs the writer to adjust the table * structure in the database. Schema updates are recorded in meta properties of the bean. * * @param OODBBean $bean bean to get cast data from and store meta in * @param string $property property to store * @param mixed $value value to store * * @return void */ private function moldTable(OODBBean $bean, $property, $value) { $table = $bean->getMeta('type'); $columns = $this->writer->getColumns($table); if (!$this->oodb->isChilled($bean->getMeta('type'))) { if ($bean->getMeta("cast.{$property}", -1) !== -1) { //check for explicitly specified types $cast = $bean->getMeta("cast.{$property}"); $typeno = $this->getTypeFromCast($cast); } else { $cast = FALSE; $typeno = $this->writer->scanType($value, TRUE); } if (isset($columns[$this->writer->esc($property, TRUE)])) { //Is this property represented in the table ? if (!$cast) { //rescan without taking into account special types >80 $typeno = $this->writer->scanType($value, FALSE); } $sqlt = $this->writer->code($columns[$this->writer->esc($property, TRUE)]); if ($typeno > $sqlt) { //no, we have to widen the database column type $this->writer->widenColumn($table, $property, $typeno); $bean->setMeta('buildreport.flags.widen', TRUE); } } else { $this->writer->addColumn($table, $property, $typeno); $bean->setMeta('buildreport.flags.addcolumn', TRUE); $this->processBuildCommands($table, $property, $bean); } } }