id; } public function setId($id) { $this->id = $id; } private $values = null; protected function getValues() { if(is_null($this->values)) $this->loadDataFromDatabase(); return $this->values; } public function isNew() { return $this->new; } public function setValues($values) { $this->values = $values; } public function markAsNew($new) { $this->new = $new; } public function fillDefaultValues() {} /** * @return AbstractCommonClubsModelFactory */ protected abstract function getFactory(); public function save() { $factory = $this->getFactory(); $attribs = $factory->getAttributes(); $db = Factory::getDbo(); $q = $db->getQuery(true); $db->transactionStart(); if($this->new) $this->prepareInsert($attribs, $factory, $q); else $this->prepareUpdate($attribs, $factory, $q); $db->setQuery($q); $db->execute(); if($this->new) { $this->finishInsert($db); } $db->transactionCommit(); } private function loadDataFromDatabase() { $factory = $this->getFactory(); $attribs = $factory->getAttributes(); $db = Factory::getDbo(); $q = $db->getQuery(true); foreach($attribs as $k => $v) { $rawColName = isset($v['col']) ? $v['col'] : $k; $q->select($q->qn($rawColName, $k)); } $q->from($factory->getTableName()); $q->where("id = {$this->id}"); $db->setQuery($q); $db->execute(); $values = $db->loadAssoc(); $values = $this->unpackExternalReferencesFromKeys($values); $this->values = $values; } private function packExternalReferencesAsKeys($vals) { foreach($this->getFactory()->getAttributes() as $k => $v) { if($v['type'] !== 'ref') continue; if(is_null($vals[$k])) continue; $vals[$k] = $vals[$k]->getId(); } return $vals; } private function unpackExternalReferencesFromKeys($vals) { foreach($this->getFactory()->getAttributes() as $k => $v) { if(empty($v['type']) || $v['type'] !== 'ref') continue; if(empty($v['ref'])) throw new Exception('External reference of unknown class found.'); if(empty($vals[$k])) continue; $vals[$k] = $this->loadExternalReferenceAsObject($v['ref'], $vals[$k]); } return $vals; } private function loadExternalReferenceAsObject($className, $value) { if(is_string($value) && preg_match('/^[0-9]+$/', $value)) $value = (int) $value; if(! is_int($value)) throw new Exception('Reference with non-integer value'); $factoryName = $this->getFactoryNameOfClass($className); $factory = new $factoryName(); return $factory->loadById($value); } private const CLASSNAME_MAP = array( ); /** * @todo This must be done better and more cleanly * @param string $className * @return AbstractCommonClubsModelFactory */ private function getFactoryNameOfClass($className) { if(empty(self::CLASSNAME_MAP[$className])) { $parts = array(); if(preg_match('/^CommonClubsModel(.*)/', $className, $parts)) { return "CommonClubsModelFactory{$parts[1]}"; } throw new Exception("Unknown mapping of class $className"); } return self::CLASSNAME_MAP[$className]; } /** * * @param array $rawData * @param array $attribs * @param JDatabaseQuery $q * @return string[]|number[]|NULL[] */ private function quoteData($rawData, $attribs, $q) { $quotedData = array(); foreach($attribs as $k => $v) { if(empty($v['type'])) $v['type'] = 'string'; if($rawData[$k] === NULL) $quotedData[$k] = 'NULL'; switch($v['type']) { case 'string': $quotedData[$k] = $q->q($rawData[$k]); break; case 'int': $quotedData[$k] = (int) $rawData[$k]; break; case 'float': $quotedData[$k] = (float) $rawData[$k]; break; case 'ref': if($v['ref'] === null) $quotedData[$k] = 'NULL'; else $quotedData[$k] = $rawData[$k]->getId(); break; } } return $quotedData; } protected function filterDatabaseRawData($values) { return $values; } protected function filterDatabaseQuotedData($quoted) { return $quoted; } /** * * @param array $attribs * @param JDatabaseQuery $q * @return array */ private function getQuotedData($attribs, $q) { $rawData = $this->getValues(); $rawData = $this->filterDatabaseRawData($rawData); $quotedData = $this->quoteData($rawData, $attribs, $q); $quotedData = $this->filterDatabaseQuotedData($quotedData); return $quotedData; } /** * * @param array $attribs * @param AbstractCommonClubsModelFactory $factory * @param JDatabaseQuery $q */ private function prepareInsert($attribs, $factory, $q) { $q->insert($factory->getTableName()); $dbcols = array(); foreach($attribs as $k => $v) { $dbcols[] = isset($v['col']) ? $v['col'] : $k; } $q->columns($q->qn($dbcols)); $quotedData = $this->getQuotedData($attribs, $q); $q->values(join(', ', $quotedData)); } /** * * @param JDatabaseDriver $db */ private function finishInsert($db) { $this->id = $db->insertid(); $this->new = false; } /** * * @param array $attribs * @param AbstractCommonClubsModelFactory $factory * @param JDatabaseQuery $q */ private function prepareUpdate($attribs, $factory, $q) { $q->update($factory->getTableName()); $dbcols = array(); foreach($attribs as $k => $v) $dbcols[] = isset($v['col']) ? $v['col'] : $k; $quotedData = $this->getQuotedData($attribs, $q); $q->set(array_map(function($col, $data){ return "$col = $data"; }, $dbcols, $quotedData)); $q->where("id = {$this->id}"); } // FIXME Add additional filter to remove associations of the object public function delete() { $db = Factory::getDbo(); $q = $db->getQuery(true); $factory = $this->getFactory(); $q->delete($factory->getTableName()); $q->where("id = {$this->id}"); $db->transactionStart(); $db->setQuery($q); $db->execute(); $db->transactionCommit(); } /** * * @param AbstractCommonClubsModelFactory $factory * @param string $colName */ protected function fetchAssociatedElements($factory, $colName) { $condition = "main.$colName = {$this->id}"; return $factory->loadElements($condition); } protected function filterPackData($values) { return $values; } protected function filterUnpackData($values) { return $values; } public function pack() { $vals = $this->getValues(); $vals = $this->packExternalReferencesAsKeys($vals); $vals = $this->filterPackData($vals); $json = json_encode($vals); return urldecode($json); } public function unpack($str) { $json = urlencode($str); $data = json_decode($json, true); $vals = $this->unpackExternalReferencesFromKeys($data); $vals = $this->filterUnpackData($vals); $this->setValues($vals); } }