From: Andrey Kutejko Date: Sun, 7 Sep 2014 21:36:36 +0000 (+0300) Subject: move to src folder X-Git-Tag: 0.6~13 X-Git-Url: https://git.andy128k.dev/?a=commitdiff_plain;h=641e20d59179539631cb50392f177c2f1f29cb44;p=ipf-legacy-orm.git move to src folder --- diff --git a/ipf/legacy_orm/adminmodel.php b/ipf/legacy_orm/adminmodel.php deleted file mode 100644 index 75d0b94..0000000 --- a/ipf/legacy_orm/adminmodel.php +++ /dev/null @@ -1,613 +0,0 @@ -modelName = $modelName; - } - - public function verbose_name() - { - return IPF_Utils::humanTitle($this->modelName); - } - - public function slug() - { - return strtolower($this->modelName); - } - - private function table() - { - return IPF_ORM::getTable($this->modelName); - } - - protected function query($searchValue, $filters) - { - if (method_exists($this->modelName, 'ordering')) { - $m = new $this->modelName; - $ord = $m->ordering(); - } elseif ($this->table()->getOrdering()) { - $ord = implode(', ', $this->table()->getOrdering()); - } elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable')) { - $ord = $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName(); - } else { - $ord = '1 desc'; - } - - $q = IPF_ORM_Query::create()->from($this->modelName)->orderby($ord); - - if ($searchValue) { - $wh = array(); - $whv = array(); - foreach ($this->_searchFields() as $f) { - $wh[] = $f.' like ?'; - $whv[] = '%'.$searchValue.'%'; - } - $q->addWhere($wh, $whv); - } - - foreach ($filters as $f) - $f->applyToQuery($q); - - return $q; - } - - protected function getItems($searchValue, $filters, $page, $pageSize) - { - $idColumn = $this->table()->getIdentifier(); - - $result = array(); - foreach ($this->query($searchValue, $filters)->limit($pageSize)->offset(($page - 1) * $pageSize)->execute() as $o) { - $id = $o->__get($idColumn); - $result[$id] = $o; - } - return $result; - } - - protected function itemsCount($searchValue, $filters) - { - return $this->query($searchValue, $filters)->count(); - } - - public function getObjectByID($id) - { - return $this->table()->find($id); - } - - public function searcheable() - { - return count($this->_searchFields()) > 0; - } - - protected function _searchFields() - { - return array(); - } - - public function list_display() - { - return $this->table()->getColumnNames(); - } - - public function renderCell($object, $column) - { - $value = $object->$column; - switch ($object->getTable()->getTypeOf($column)) { - case 'boolean': - return $value - ? '' - : ''; - case 'timestamp': - return Text::escape(IPF_Utils::formatDate($value)); - default: - return Text::escape($value); - } - } - - protected function columnTitle($column) - { - $columns = $this->table()->getColumns(); - if (array_key_exists($column, $columns) && array_key_exists('verbose', $columns[$column])) - return $columns[$column]['verbose']; - else - return parent::columnTitle($column); - } - - public function _orderable() - { - return $this->_orderableColumn() !== null; - } - - public function _orderableColumn() - { - if (method_exists($this, 'list_order')) - return $this->list_order(); - elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable')) - return $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName(); - else - return null; - } - - /* filters */ - - protected function _listFilters() - { - return array(); - } - - public function listFilters() - { - $filters = array(); - foreach ($this->_listFilters() as $f) { - if (is_string($f)) - $f = new IPF_Admin_Model_RelationFilter($this->modelName, $f); - - $filters[] = $f; - } - return $filters; - } - - public function reorder($ids) - { - if (!$this->_orderable()) - return false; - - $ord_field = $this->_orderableColumn(); - - $table = IPF_ORM::getTable($this->modelName); - $conn = $table->getConnection(); - - $idColumn = $table->getIdentifier(); - if (is_array($idColumn)) - $idColumn = $idColumn[0]; - - $questions = str_repeat('?,', count($ids)-1) . '?'; - $query = 'SELECT ' . $conn->quoteIdentifier($ord_field) . - ' FROM ' . $conn->quoteIdentifier($table->getTableName()) . - ' WHERE ' . $conn->quoteIdentifier($idColumn) . ' IN (' . $questions . ')' . - ' ORDER BY ' . $conn->quoteIdentifier($ord_field); - $ords = $conn->fetchColumn($query, $ids); - - $i = 0; - foreach ($ids as $id) { - $item = $table->find($id); - $item[$ord_field] = $ords[$i]; - $item->save(); - $i++; - } - - return true; - } - - /* edit */ - - protected function _getForm($model, $data) - { - $extra = array( - 'user_fields' => $this->fields(), - 'inlines' => $this->inlines(), - ); - return new IPF_Admin_ModelForm($data, $model, $this->modelName, $extra); - } - - public function inlines() - { - return array(); - } - - public function saveObject($form, $model) - { - $model = $form->save(); - $idColumn = $this->table()->getIdentifier(); - return array($model->$idColumn, $model); - } - - public function deleteObject($model) - { - $model->delete(); - } - - public function fields() - { - return null; - } -} - -class IPF_Admin_ModelForm extends IPF_Form_Model -{ - private $inlines = array(); - - function __construct($data=null, $model=null, $modelClass='', $extra=array()) - { - $editMode = $model !== null; - - if (!$model) - $model = new $modelClass; - - $extra['model'] = $model; - - if ($extra['inlines']) { - foreach ($extra['inlines'] as $inlineClassName) { - $this->inlines[] = new $inlineClassName($model); - } - } - - if ($editMode) { - $extra['initial'] = $this->getFormData($model, $this->inlines); - } - - parent::__construct($data, $extra); - } - - function initFields($extra=array()) - { - parent::initFields($extra); - - if ($this->inlines) { - $this->field_groups[] = array('fields' => array_keys($this->fields)); - } - - foreach ($this->inlines as $inline) { - $name = $inline->getModelName(); - $this->fields[$name] = $inline->createField(); - - $this->field_groups[] = array( - 'label' => $inline->getLegend(), - 'fields' => array($name), - ); - } - } - - private function getFormData($o, $inlines) - { - $data = $o->getData(); - foreach ($o->getTable()->getRelations() as $rname => $rel) { - $fields = $this->fields(); - if (!$fields || in_array($rname, $fields)) { - if ($rel->getType() == IPF_ORM_Relation::MANY_AGGREGATE) { - $data[$rname] = array(); - foreach ($rel->fetchRelatedFor($o) as $ri) { - $data[$rname][] = $ri->pk(); - } - } - } - } - - foreach ($inlines as $inline) { - $objs = array(); - foreach ($inline->getObjects() as $io) { - $d = $io->getData(); - $d['id'] = $io->pk(); - $objs[] = $d; - } - $data[$inline->getModelName()] = $objs; - } - - return $data; - } - - function save($commit=true) - { - $model = parent::save($commit); - - foreach ($this->inlines as $inline) { - $this->saveInlineObject($inline, $model); - } - - return $model; - } - - private function saveInlineObject($inline, $model) - { - $objIndex = array(); - foreach ($inline->getObjects() as $obj) - $objIndex[$obj->pk()] = $obj; - - $modelName = $inline->getModelName(); - $fk_local = $inline->getFkLocal(); - - foreach ($this->cleaned_data[$modelName] as $objData) { - if (array_key_exists('id', $objData) && array_key_exists($objData['id'], $objIndex)) { - $obj = $objIndex[$objData['id']]; - if (isset($objData['is_remove'])) { - $obj->delete(); - } else { - $obj->synchronizeWithArray($objData); - $obj->save(); - } - } else { - $objData[$fk_local] = $model->id; - $obj = new $modelName; - $obj->synchronizeWithArray($objData); - $obj->save(); - } - } - } -} - -abstract class IPF_Admin_Model_Filter implements IPF_Admin_ListFilter -{ - abstract function applyToQuery($q); - - protected $title, $paramName; - - function title() - { - return $this->title; - } - - protected function link($params, $value, $title) - { - if ($value !== null) - $params[$this->paramName] = $value; - else - unset($params[$this->paramName]); - - return Tag::a() - ->attr('href', '?'.IPF_HTTP_URL::generateParams($params, false)) - ->append($title); - } -} - -class IPF_Admin_Model_RelationFilter extends IPF_Admin_Model_Filter -{ - private $relation, $selected = null; - - function __construct($modelName, $relName) - { - $this->relation = IPF_ORM::getTable($modelName)->getRelation($relName); - $this->title = 'By '.IPF_Utils::humanTitle($relName); - - $this->paramName = 'filter_'.$this->relation['local']; - } - - function setParams($params) - { - $this->selected = \PFF\Arr::get($params, $this->paramName); - } - - function applyToQuery($q) - { - // check ??? - if ($this->selected) - $q->addWhere($this->relation['local'].' = ?', array($this->selected)); - } - - function render($extraParams) - { - $ul = Tag::ul(); - - // reset filter - $ul->append(Tag::li() - ->toggleClass('selected', !$this->selected) - ->append($this->link($extraParams, null, __('All')))); - - // query related - $table = IPF_ORM::getTable($this->relation['class']); - - $query = $table->createQuery(); - if ($table->getOrdering()) { - $query->orderBy(implode(', ', $table->getOrdering())); - } elseif ($table->hasTemplate('IPF_ORM_Template_Orderable')) { - $query->orderBy($table->getTemplate('IPF_ORM_Template_Orderable')->getColumnName()); - } - - $foreign = $this->relation['foreign']; - foreach ($query->execute() as $val) { - $id = $val[$foreign]; - $name = (string)$val; - - $ul->append(Tag::li() - ->toggleClass('selected', $this->selected == $id) - ->append($this->link($extraParams, $id, $name))); - } - - return $ul->html(); - } -} - -class IPF_Admin_Model_OwnedFilter extends IPF_Admin_Model_Filter -{ - private $column, $selected = null; - - function __construct($modelName, $column, $title) - { - $this->column = $column; - $this->title = $title; - $this->paramName = 'filter_'.$this->column; - } - - function setParams($params) - { - $this->selected = \PFF\Arr::get($params, $this->paramName); - } - - function applyToQuery($q) - { - // check ??? - if ($this->selected) - $q->addWhere($this->column.' = ?', array($this->selected)); - } - - function render($extraParams) - { - $ul = Tag::ul(); - - // reset filter - $ul->append(Tag::li() - ->toggleClass('selected', !$this->selected) - ->append($this->link($extraParams, null, __('All')))); - - foreach (User::query()->fetchAll() as $val) { - $id = $val->id; - $name = (string)$val; - - $ul->append(Tag::li() - ->toggleClass('selected', $this->selected == $id) - ->append($this->link($extraParams, $id, $name))); - } - - return $ul->html(); - } -} - -class BooleanFilter extends IPF_Admin_Model_Filter -{ - private $column, $trueTitle, $falseTitle, $selected; - - public function __construct($column, $title, $trueTitle='Yes', $falseTitle='No') - { - $this->column = $column; - $this->title = $title; - $this->trueTitle = $trueTitle; - $this->falseTitle = $falseTitle; - - $this->paramName = 'filter_'.$column; - } - - function setParams($params) - { - $this->selected = \PFF\Arr::get($params, $this->paramName); - } - - function render($extraParams) - { - return Tag::ul(null, - Tag::li() - ->toggleClass('selected', $this->selected !== 'y' && $this->selected !== 'n') - ->append($this->link($extraParams, null, __('All'))), - Tag::li() - ->toggleClass('selected', $this->selected === 'y') - ->append($this->link($extraParams, 'y', $this->trueTitle)), - Tag::li() - ->toggleClass('selected', $this->selected === 'n') - ->append($this->link($extraParams, 'n', $this->falseTitle))); - } - - function applyToQuery($q) - { - switch ($this->selected) { - case 'y': - $query->addWhere($this->column); - break; - case 'n': - $query->addWhere('NOT '.$this->column); - break; - } - } -} - -class DateHierarchyListFilter extends IPF_Admin_Model_Filter -{ - private $model, $name; - private $day, $month, $year; - - function __construct($title, $modelName, $fieldName) - { - $this->title = $title; - $this->modelName = $modelName; - $this->name = $fieldName; - - $this->paramName = $fieldName; - } - - function setParams($params) - { - $date = \PFF\Arr::get($params, $this->paramName, ''); - $matches = array(); - if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $date, $matches)) { - $this->year = intval($matches[1]); - $this->month = intval($matches[2]); - $this->day = intval($matches[3]); - } - } - - function render($extraParams) - { - $ul = Tag::ul(); - - $ul->append(Tag::li() - ->toggleClass('selected', !$this->year) - ->append($this->link($extraParams, null, __('All')))); - - if ($this->year) - $ul->append($this->renderChoice($extraParams, $this->year, 0, 0, $this->year)->addClass('selected')); - - if ($this->month) - $ul->append($this->renderChoice($extraParams, $this->year, $this->month, 0, $this->monthName($this->month))->addClass('selected')); - - if ($this->day) - $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $this->day, $this->day)->addClass('selected')); - - if ($this->day) { - } elseif ($this->month) { - $days = $this->choices('DAY(' . $this->name . ')', - 'YEAR(' . $this->name . ')', $this->year, - 'MONTH(' . $this->name . ')', $this->month); - foreach ($days as $day) { - $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $day, $day)); - } - } elseif ($this->year) { - $months = $this->choices('MONTH(' . $this->name . ')', - 'YEAR(' . $this->name . ')', $this->year); - foreach ($months as $month) { - $ul->append($this->renderChoice($extraParams, $this->year, $month, 0, $this->monthName($month))); - } - } else { - $years = $this->choices('YEAR(' . $this->name . ')'); - foreach ($years as $year) { - $ul->append($this->renderChoice($extraParams, $year, 0, 0, $year)); - } - } - - return $ul->html(); - } - - function applyToQuery($q) - { - if ($this->day) - $q->addWhere('DAY(' . $this->name . ') = ?', array($this->day)); - if ($this->month) - $q->addWhere('MONTH(' . $this->name . ') = ?', array($this->month)); - if ($this->year) - $q->addWhere('YEAR(' . $this->name . ') = ?', array($this->year)); - } - - private function choices(/* $what, [$expr, $value, ...]*/) - { - $args = func_get_args(); - $what = array_shift($args); - - $q = IPF_ORM_Query::create() - ->from($this->modelName) - ->select($what . ' AS value') - ->groupBy('1') - ->orderBy('1'); - - while ($args) { - $expr = array_shift($args); - $value = array_shift($args); - - $q->addWhere($expr . ' = ?', array($value)); - } - return \PFF\Arr::create($q->fetchArray())->pluck('value')->arr(); - } - - private function renderChoice($extraParams, $year, $month, $day, $label) - { - return Tag::li(null, - $this->link($extraParams, sprintf('%04d-%02d-%02d', $year, $month, $day), $label)); - } - - protected function monthName($month) - { - return date('F', mktime(0, 0, 0, $month, 1)); - } -} - diff --git a/ipf/legacy_orm/adminmodelinline.php b/ipf/legacy_orm/adminmodelinline.php deleted file mode 100644 index bb5e4dd..0000000 --- a/ipf/legacy_orm/adminmodelinline.php +++ /dev/null @@ -1,110 +0,0 @@ -parentModel = $parentModel; - } - - abstract function getModelName(); - - public function getApplication() - { - foreach (IPF_Project::getInstance()->appList() as $app) - foreach (IPF_Legacy_ORM_App::appModelList($app) as $model) - if ($model == $this->getModelName()) - return $app; - return null; - } - - function getAddNum() - { - return 4; - } - - function getLegend() - { - return $this->getModelName(); - } - - function getFkName() - { - foreach($this->getTable()->getRelations() as $rel) { - if ($rel->getClass()==get_class($this->parentModel)) - return $rel->getAlias(); - } - throw new IPF_Exception(__('Cannot get fkName for '.$this->getModelName())); - } - - function getFkLocal() - { - foreach($this->getTable()->getRelations() as $rel) { - if ($rel->getClass() == get_class($this->parentModel)) - return $rel->getLocal(); - } - throw new IPF_Exception(__('Cannot get fkLocal for '.$this->getModelName())); - } - - function getObjects() - { - if (!$this->parentModel->exists()) - return array(); - - $query = IPF_ORM_Query::create() - ->from($this->getModelName()) - ->where($this->getFkLocal().'='.$this->parentModel->id); - - $o = $this->_orderableColumn(); - if ($o) - $query->orderby($o); - else - $query->orderby($this->orderby); - - return $query->execute(); - } - - public function _orderable() - { - return $this->_orderableColumn() !== null; - } - - public function _orderableColumn() - { - if ($this->getTable()->hasTemplate('IPF_ORM_Template_Orderable')) - return $this->getTable()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName(); - else - return null; - } - - private function getTable() - { - return IPF_ORM::getTable($this->getModelName()); - } - - public function createField() - { - $exclude = array( - $this->getFkName(), - $this->getFkLocal(), - ); - $o = $this->_orderableColumn(); - if ($o) - $exclude[] = $o; - - $fields = array(); - foreach (IPF_Form_Model::suggestFields($this->getTable(), null, $exclude, null) as $field) { - list($n, $f) = $field; - $fields[$n] = $f; - } - - return new IPF_Form_Field_Set(array( - 'fields' => $fields, - 'addCount' => $this->getAddNum(), - )); - } -} - diff --git a/ipf/legacy_orm/app.php b/ipf/legacy_orm/app.php deleted file mode 100644 index 8db81a0..0000000 --- a/ipf/legacy_orm/app.php +++ /dev/null @@ -1,38 +0,0 @@ -getName(), self::$appModels)) { - $models = array(); - try { - $it = new DirectoryIterator($app->getPath().DIRECTORY_SEPARATOR.'models'); - foreach ($it as $file) { - $e = explode('.', $file->getFileName(), 2); - if (count($e) == 2 && $e[1] === 'php') { - $models[] = $e[0]; - } - } - } catch (RuntimeException $e) { - // nothing to do - } - - self::$appModels[$app->getName()] = $models; - } - return self::$appModels[$app->getName()]; - } - - public function commands() - { - return array( - new IPF_Legacy_ORM_Command_BuildModels, - new IPF_Legacy_ORM_Command_Sql, - new IPF_Legacy_ORM_Command_SyncDB, - new IPF_Legacy_ORM_Command_Fixtures, - ); - } -} - diff --git a/ipf/legacy_orm/commands/buildmodels.php b/ipf/legacy_orm/commands/buildmodels.php deleted file mode 100644 index 9dca874..0000000 --- a/ipf/legacy_orm/commands/buildmodels.php +++ /dev/null @@ -1,21 +0,0 @@ -appList() as $app) - $paths[] = $app->getPath(); - - IPF_ORM::generateModelsFromYaml($paths, array()); - } -} - diff --git a/ipf/legacy_orm/commands/fixtures.php b/ipf/legacy_orm/commands/fixtures.php deleted file mode 100644 index 3c6e279..0000000 --- a/ipf/legacy_orm/commands/fixtures.php +++ /dev/null @@ -1,68 +0,0 @@ -appList() as $app) - $paths[] = $app->path; - - $fixtures = array(); - foreach ($paths as $path) { - $path .= DIRECTORY_SEPARATOR.'fixtures.php'; - if (is_file($path)) - $fixtures = array_merge($fixtures, require $path); - } - - if (!count($fixtures)) { - echo "No fixtures found\n"; - return; - } - - foreach ($fixtures as $fixture) { - $modelClass = $fixture['model']; - $key = $fixture['key']; - if (!is_array($key)) - $key = array($key); - $records = $fixture['records']; - echo "Loading $modelClass "; - $table = IPF_ORM::getTable($modelClass); - $table->getConnection()->beginTransaction(); - - $query = $table - ->createQuery() - ->limit(1); - foreach ($key as $k) - $query->addWhere($k . ' = ?'); - - foreach ($records as $record) { - $params = array(); - foreach ($key as $k) - $params[] = $record[$k]; - - $model = $query->execute($params); - if ($model) - $model = $model[0]; - else - $model = new $modelClass; - - foreach ($record as $k => $v) - $model->$k = $v; - - $model->save(); - echo '.'; - } - $table->getConnection()->commit(); - echo "\n"; - } - } -} - diff --git a/ipf/legacy_orm/commands/sql.php b/ipf/legacy_orm/commands/sql.php deleted file mode 100644 index 31f55c9..0000000 --- a/ipf/legacy_orm/commands/sql.php +++ /dev/null @@ -1,16 +0,0 @@ -appList() as $app) - print IPF_ORM::generateSqlFromModels($app)."\n"; - } -} - diff --git a/ipf/legacy_orm/commands/syncdb.php b/ipf/legacy_orm/commands/syncdb.php deleted file mode 100644 index 02b550f..0000000 --- a/ipf/legacy_orm/commands/syncdb.php +++ /dev/null @@ -1,21 +0,0 @@ -appList() as $app) - $models = array_merge($models, IPF_Legacy_ORM_App::appModelList($app)); - - IPF_ORM::createTablesFromModels($models); - } -} - diff --git a/ipf/legacy_orm/modelform.php b/ipf/legacy_orm/modelform.php deleted file mode 100644 index fe9cb93..0000000 --- a/ipf/legacy_orm/modelform.php +++ /dev/null @@ -1,223 +0,0 @@ -model = $extra['model']; - else - throw new IPF_Exception_Form(__('Unknown model for form')); - - if (isset($extra['user_fields'])) - $this->user_fields = $extra['user_fields']; - - $exclude = \PFF\Arr::get($extra, 'exclude', array()); - $fields = self::suggestFields($this->model->getTable(), $this->fields(), $exclude, $this); - foreach ($fields as $field) { - list($n, $f) = $field; - $this->fields[$n] = $f; - } - } - - public static function suggestFields($table, $fields=null, $exclude=array(), $form=null) - { - $result = array(); - - $db_columns = $table->getColumns(); - $db_relations = $table->getRelations(); - - if ($fields === null) { - foreach ($db_columns as $name => $col) { - if (array_search($name, $exclude) !== false) - continue; - if (isset($col['exclude']) && $col['exclude']) - continue; - - if ($form && method_exists($form, 'add__'.$name.'__field')) { - $f = call_user_func(array($form, 'add__'.$name.'__field')); - if ($f) - $field = array($name, $f); - } else { - $field = self::createDBField($name, $table, $col); - } - - if ($field) - $result[] = $field; - } - - foreach ($db_relations as $name => $relation) { - if (array_search($name, $exclude) !== false) - continue; - if (isset($relation['exclude']) && $relation['exclude']) - continue; - - if ($form && method_exists($form, 'add__'.$name.'__field')) { - $f = call_user_func(array($form, 'add__'.$name.'__field')); - if ($f) - $field = array($name, $f); - } else { - $field = self::createDBRelation($name, $relation, $db_columns); - } - - if ($field) - $result[] = $field; - } - } else { - foreach ($fields as $uname) { - $field = null; - if ($form && method_exists($form, 'add__'.$uname.'__field')) { - $f = call_user_func(array($form, 'add__'.$uname.'__field')); - if ($f) - $field = array($uname, $f); - } elseif (array_key_exists($uname, $db_columns)) { - $field = self::createDBField($uname, $table, $db_columns[$uname]); - } elseif (array_key_exists($uname, $db_relations)) { - $field = self::createDBRelation($uname, $db_relations[$uname], $db_columns); - } else { - throw new IPF_Exception_Form(sprintf(__("Model '%s' has no column '%s'."), $table->getComponentName(), $uname)); - } - if ($field) - $result[] = $field; - } - } - return $result; - } - - public static function createDBField($name, $table, $col) - { - if ($name == $table->getIdentifier()) - return null; - - $required = isset($col['notblank']) && $col['notblank']; - $label = isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name); - - $params = array( - 'required' => $required, - 'label' => $label, - 'help_text' => '', - ); - - switch ($col['type']) { - case 'string': - if (isset($col['length'])) - $params['max_length'] = (int)($col['length']); - - if (isset($col['uploadTo'])) - $params['uploadTo'] = $col['uploadTo']; - - if (isset($col['email']) && $col['email']) { $form_field = new IPF_Form_Field_Email($params); } - elseif (isset($col['file']) && $col['file']) { $form_field = new IPF_Form_Field_File($params); } - elseif (isset($col['image']) && $col['image']) { $form_field = new IPF_Form_Field_Image($params); } - elseif (isset($col['html']) && $col['html']) { $form_field = new IPF_Form_Field_Html($params); } - else { $form_field = new IPF_Form_Field_Varchar($params); } - - break; - case 'boolean': - $form_field = new IPF_Form_Field_Boolean($params); - break; - case 'integer': - $params['widget_attrs'] = array('style' => 'width:140px;'); - $form_field = new IPF_Form_Field_Integer($params); - break; - case 'double': - case 'decimal': - $params['widget_attrs'] = array('style' => 'width:140px;'); - $form_field = new IPF_Form_Field_Float($params); - break; - case 'date': - $format = IPF::get('date_format'); - if ($format) - $params['widget_attrs'] = array('format' => $format); - $form_field = new IPF_Form_Field_Date($params); - break; - case 'datetime': - case 'timestamp': - $format = IPF::get('datetime_format'); - if ($format) - $params['widget_attrs'] = array('format' => $format); - $form_field = new IPF_Form_Field_Datetime($params); - break; - default: - throw new IPF_Exception_Form(__('Unsupported column type \''.$col['type'].'\'.')); - } - - if ($form_field !== null) - return array($name, $form_field); - else - return null; - } - - public static function createDBRelation($name, $relation, $db_columns) - { - $rt = $relation->getType(); - if ($rt !== IPF_ORM_Relation::ONE_AGGREGATE && $rt !== IPF_ORM_Relation::MANY_AGGREGATE) - return null; - - $lfn = $relation->getLocalFieldName(); - if (isset($db_columns[$lfn])) - $col = $db_columns[$lfn]; - else - $col = array(); - - $table = IPF_ORM::getTable($relation->getClass()); - - $params = array( - 'required' => isset($col['notblank']), - 'label' => isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name), - 'help_text' => '', - 'table' => $table, - 'model' => $relation->getClass(), - ); - - if ($rt === IPF_ORM_Relation::ONE_AGGREGATE) { - $choices = array('--------' => ''); - $pk = $table->getIdentifier(); - foreach ($table->findAll() as $o) { - $choices[$o->__toString()] = $o->$pk; - } - - $params['choices'] = $choices; - return array($name.'_id', new IPF_Form_Field_Choice($params)); - } elseif ($rt === IPF_ORM_Relation::MANY_AGGREGATE) { - $choices = array(); - $pk = $table->getIdentifier(); - foreach ($table->findAll() as $o) { - $choices[$o->__toString()] = $o->$pk; - } - - $params['choices'] = $choices; - return array($name, new IPF_Form_Field_MultipleChoice($params)); - } else { - return null; - } - } - - function fields() - { - return $this->user_fields; - } - - function save($commit=true) - { - if (!$this->isValid()) - throw new IPF_Exception_Form(__('Cannot save the model from an invalid form.')); - - $this->model->SetFromFormData($this->cleaned_data); - $this->model->save(); - $rels = $this->model->getTable()->getRelations(); - foreach ($rels as $rname => $rel) { - if (isset($this->cleaned_data[$rname])) { - $this->model->unlink($rel->getAlias()); - if (is_array($this->cleaned_data[$rname])) { - $this->model->link($rel->getAlias(),$this->cleaned_data[$rname]); - } - } - } - return $this->model; - } -} - diff --git a/ipf/legacy_orm/orm.php b/ipf/legacy_orm/orm.php deleted file mode 100644 index f5fb916..0000000 --- a/ipf/legacy_orm/orm.php +++ /dev/null @@ -1,255 +0,0 @@ -getTable($componentName); - } - - public static function generateModelsFromYaml($directory, $extraAllwedReferences=array()) - { - $files = array(); - foreach ((array)$directory as $dir) - $files[] = $dir . '/models.yml'; - - // load schema - $import = new IPF_ORM_Import_Schema; - $definitions = $import->importSchema($files, $extraAllwedReferences); - - // build - $models = array(); - foreach ($definitions as $name => $definition) { - print " $name\n"; - $targetPath = substr($definition['input_file'], 0, -4); - - $builder = new IPF_ORM_Import_Builder; - $builder->buildRecord($definition, $targetPath); - - $models[] = $name; - } - - return $models; - } - - public static function createTablesFromModels($models) - { - IPF_ORM_Manager::connection()->export->exportClasses($models); - } - - public static function generateSqlFromModels($app) - { - $sql = IPF_ORM_Manager::connection()->export->exportSortedClassesSql(IPF_Legacy_ORM_App::appModelList($app), false); - - $build = ''; - foreach ($sql as $query) { - $build .= $query.";\n\n"; - } - return $build; - } - - public static function dump($var, $output = true, $indent = "") - { - $ret = array(); - switch (gettype($var)) { - case 'array': - $ret[] = 'Array('; - $indent .= " "; - foreach ($var as $k => $v) { - - $ret[] = $indent . $k . ' : ' . self::dump($v, false, $indent); - } - $indent = substr($indent,0, -4); - $ret[] = $indent . ")"; - break; - case 'object': - $ret[] = 'Object(' . get_class($var) . ')'; - break; - default: - $ret[] = var_export($var, true); - } - - if ($output) { - print implode("\n", $ret); - } - - return implode("\n", $ret); - } - - public static function GetObjectOr404($object, $id) - { - $obj = IPF_ORM::getTable($object)->findOneById($id); - if ($obj) - return $obj; - throw new IPF_HTTP_Error404(); - } -} - diff --git a/ipf/legacy_orm/orm/access.php b/ipf/legacy_orm/orm/access.php deleted file mode 100644 index d0a87d2..0000000 --- a/ipf/legacy_orm/orm/access.php +++ /dev/null @@ -1,82 +0,0 @@ - $v) { - $this->set($k, $v); - } - - return $this; - } - - public function __set($name, $value) - { - $this->set($name, $value); - } - - public function __get($name) - { - return $this->get($name); - } - - public function __isset($name) - { - return $this->contains($name); - } - - public function __unset($name) - { - return $this->remove($name); - } - - public function offsetExists($offset) - { - return $this->contains($offset); - } - - public function offsetGet($offset) - { - return $this->get($offset); - } - - public function offsetSet($offset, $value) - { - if ( ! isset($offset)) { - $this->add($value); - } else { - $this->set($offset, $value); - } - } - - public function offsetUnset($offset) - { - return $this->remove($offset); - } - - public function remove($offset) - { - throw new IPF_ORM_Exception('Remove is not supported for ' . get_class($this)); - } - - public function get($offset) - { - throw new IPF_ORM_Exception('Get is not supported for ' . get_class($this)); - } - - public function set($offset, $value) - { - throw new IPF_ORM_Exception('Set is not supported for ' . get_class($this)); - } - - public function contains($offset) - { - throw new IPF_ORM_Exception('Contains is not supported for ' . get_class($this)); - } - - public function add($value) - { - throw new IPF_ORM_Exception('Add is not supported for ' . get_class($this)); - } -} diff --git a/ipf/legacy_orm/orm/adapter.php b/ipf/legacy_orm/orm/adapter.php deleted file mode 100644 index 363cade..0000000 --- a/ipf/legacy_orm/orm/adapter.php +++ /dev/null @@ -1,73 +0,0 @@ -_table = $table; - - if ($keyColumn === null) { - $keyColumn = $table->getBoundQueryPart('indexBy'); - } - - if ($keyColumn === null) { - $keyColumn = $table->getAttribute(IPF_ORM::ATTR_COLL_KEY); - } - - if ($keyColumn !== null) { - $this->keyColumn = $keyColumn; - } - } - - public function getTable() - { - return $this->_table; - } - - public function setData(array $data) - { - $this->data = $data; - } - - public function serialize() - { - $vars = get_object_vars($this); - - unset($vars['reference']); - unset($vars['reference_field']); - unset($vars['relation']); - unset($vars['expandable']); - unset($vars['expanded']); - unset($vars['generator']); - - $vars['_table'] = $vars['_table']->getComponentName(); - - return serialize($vars); - } - - public function unserialize($serialized) - { - $connection = IPF_ORM_Manager::connection(); - - $array = unserialize($serialized); - - foreach ($array as $name => $values) { - $this->$name = $values; - } - - $this->_table = $connection->getTable($this->_table); - - $keyColumn = isset($array['keyColumn']) ? $array['keyColumn'] : null; - if ($keyColumn === null) { - $keyColumn = $this->_table->getBoundQueryPart('indexBy'); - } - - if ($keyColumn !== null) { - $this->keyColumn = $keyColumn; - } - } - - public function setKeyColumn($column) - { - $this->keyColumn = $column; - - return $this; - } - - public function getKeyColumn() - { - return $this->keyColumn; - } - - public function getData() - { - return $this->data; - } - - public function getFirst() - { - return reset($this->data); - } - - public function getLast() - { - return end($this->data); - } - - public function end() - { - return end($this->data); - } - - public function key() - { - return key($this->data); - } - - public function setReference(IPF_ORM_Record $record, IPF_ORM_Relation $relation) - { - $this->reference = $record; - $this->relation = $relation; - - if ($relation instanceof IPF_ORM_Relation_ForeignKey || - $relation instanceof IPF_ORM_Relation_LocalKey) { - - $this->referenceField = $relation->getForeignFieldName(); - - $value = $record->get($relation->getLocalFieldName()); - - foreach ($this->data as $record) { - if ($value !== null) { - $record->set($this->referenceField, $value, false); - } else { - $record->set($this->referenceField, $this->reference, false); - } - } - } elseif ($relation instanceof IPF_ORM_Relation_Association) { - - } - } - - public function getReference() - { - return $this->reference; - } - - public function remove($key) - { - $removed = $this->data[$key]; - - unset($this->data[$key]); - return $removed; - } - - public function contains($key) - { - return isset($this->data[$key]); - } - - public function search(IPF_ORM_Record $record) - { - return array_search($record, $this->data, true); - } - - public function get($key) - { - if ( ! isset($this->data[$key])) { - $record = $this->_table->create(); - - if (isset($this->referenceField)) { - $value = $this->reference->get($this->relation->getLocalFieldName()); - - if ($value !== null) { - $record->set($this->referenceField, $value, false); - } else { - $record->set($this->referenceField, $this->reference, false); - } - } - if ($key === null) { - $this->data[] = $record; - } else { - $this->data[$key] = $record; - } - - if (isset($this->keyColumn)) { - $record->set($this->keyColumn, $key); - } - - return $record; - } - - return $this->data[$key]; - } - - public function getPrimaryKeys() - { - $list = array(); - $name = $this->_table->getIdentifier(); - - foreach ($this->data as $record) { - if (is_array($record) && isset($record[$name])) { - $list[] = $record[$name]; - } else { - $list[] = $record->getIncremented(); - } - } - return $list; - } - - public function getKeys() - { - return array_keys($this->data); - } - - public function count() - { - return count($this->data); - } - - public function set($key, $record) - { - if (isset($this->referenceField)) { - $record->set($this->referenceField, $this->reference, false); - } - - $this->data[$key] = $record; - } - - public function add($record, $key = null) - { - if (isset($this->referenceField)) { - $value = $this->reference->get($this->relation->getLocalFieldName()); - - if ($value !== null) { - $record->set($this->referenceField, $value, false); - } else { - $record->set($this->referenceField, $this->reference, false); - } - } - /** - * for some weird reason in_array cannot be used here (php bug ?) - * - * if used it results in fatal error : [ nesting level too deep ] - */ - foreach ($this->data as $val) { - if ($val === $record) { - return false; - } - } - - if (isset($key)) { - if (isset($this->data[$key])) { - return false; - } - $this->data[$key] = $record; - return true; - } - - if (isset($this->keyColumn)) { - $value = $record->get($this->keyColumn); - if ($value === null) { - throw new IPF_ORM_Exception("Couldn't create collection index. Record field '".$this->keyColumn."' was null."); - } - $this->data[$value] = $record; - } else { - $this->data[] = $record; - } - - return true; - } - - public function merge(IPF_ORM_Collection $coll) - { - $localBase = $this->getTable()->getComponentName(); - $otherBase = $coll->getTable()->getComponentName(); - - if ($otherBase != $localBase && !is_subclass_of($otherBase, $localBase) ) { - throw new IPF_ORM_Exception("Can't merge collections with incompatible record types"); - } - - foreach ($coll->getData() as $record) { - $this->add($record); - } - - return $this; - } - - public function loadRelated($name = null) - { - $list = array(); - $query = new IPF_ORM_Query($this->_table->getConnection()); - - if ( ! isset($name)) { - foreach ($this->data as $record) { - $value = $record->getIncremented(); - if ($value !== null) { - $list[] = $value; - } - } - $query->from($this->_table->getComponentName()); - $query->where($this->_table->getComponentName() . '.id IN (' . substr(str_repeat("?, ", count($list)),0,-2) . ')'); - - return $query; - } - - $rel = $this->_table->getRelation($name); - - if ($rel instanceof IPF_ORM_Relation_LocalKey || $rel instanceof IPF_ORM_Relation_ForeignKey) { - foreach ($this->data as $record) { - $list[] = $record[$rel->getLocal()]; - } - } else { - foreach ($this->data as $record) { - $value = $record->getIncremented(); - if ($value !== null) { - $list[] = $value; - } - } - } - - $dql = $rel->getRelationDql(count($list), 'collection'); - - $coll = $query->query($dql, $list); - - $this->populateRelated($name, $coll); - } - - public function populateRelated($name, IPF_ORM_Collection $coll) - { - $rel = $this->_table->getRelation($name); - $table = $rel->getTable(); - $foreign = $rel->getForeign(); - $local = $rel->getLocal(); - - if ($rel instanceof IPF_ORM_Relation_LocalKey) { - foreach ($this->data as $key => $record) { - foreach ($coll as $k => $related) { - if ($related[$foreign] == $record[$local]) { - $this->data[$key]->setRelated($name, $related); - } - } - } - } elseif ($rel instanceof IPF_ORM_Relation_ForeignKey) { - foreach ($this->data as $key => $record) { - if ( ! $record->exists()) { - continue; - } - $sub = new IPF_ORM_Collection($table); - - foreach ($coll as $k => $related) { - if ($related[$foreign] == $record[$local]) { - $sub->add($related); - $coll->remove($k); - } - } - - $this->data[$key]->setRelated($name, $sub); - } - } elseif ($rel instanceof IPF_ORM_Relation_Association) { - $identifier = $this->_table->getIdentifier(); - $asf = $rel->getAssociationFactory(); - $name = $table->getComponentName(); - - foreach ($this->data as $key => $record) { - if ( ! $record->exists()) { - continue; - } - $sub = new IPF_ORM_Collection($table); - foreach ($coll as $k => $related) { - if ($related->get($local) == $record[$identifier]) { - $sub->add($related->get($name)); - } - } - $this->data[$key]->setRelated($name, $sub); - - } - } - } - - public function getNormalIterator() - { - return new IPF_ORM_Collection_Iterator_Normal($this); - } - - public function takeSnapshot() - { - $this->_snapshot = $this->data; - - return $this; - } - - public function getSnapshot() - { - return $this->_snapshot; - } - - public function processDiff() - { - foreach (array_udiff($this->_snapshot, $this->data, array($this, "compareRecords")) as $record) { - $record->delete(); - } - - return $this; - } - - public function toArray($deep = false, $prefixKey = false) - { - $data = array(); - foreach ($this as $key => $record) { - - $key = $prefixKey ? get_class($record) . '_' .$key:$key; - - $data[$key] = $record->toArray($deep, $prefixKey); - } - - return $data; - } - - public function fromArray($array, $deep = true) - { - $data = array(); - foreach ($array as $rowKey => $row) { - $this[$rowKey]->fromArray($row, $deep); - } - } - - public function synchronizeWithArray(array $array) - { - foreach ($this as $key => $record) { - if (isset($array[$key])) { - $record->synchronizeWithArray($array[$key]); - unset($array[$key]); - } else { - // remove records that don't exist in the array - $this->remove($key); - } - } - // create new records for each new row in the array - foreach ($array as $rowKey => $row) { - $this[$rowKey]->fromArray($row); - } - } - public function synchronizeFromArray(array $array) - { - return $this->synchronizeWithArray($array); - } - - public function getDeleteDiff() - { - return array_udiff($this->_snapshot, $this->data, array($this, 'compareRecords')); - } - - public function getInsertDiff() - { - return array_udiff($this->data, $this->_snapshot, array($this, "compareRecords")); - } - - protected function compareRecords($a, $b) - { - if ($a->getOid() == $b->getOid()) { - return 0; - } - - return ($a->getOid() > $b->getOid()) ? 1 : -1; - } - - public function save(IPF_ORM_Connection $conn = null) - { - if ($conn == null) { - $conn = $this->_table->getConnection(); - } - - $conn->beginInternalTransaction(); - - $conn->transaction->addCollection($this); - - $this->processDiff(); - - foreach ($this->getData() as $key => $record) { - $record->save($conn); - } - - $conn->commit(); - - return $this; - } - - public function delete(IPF_ORM_Connection $conn = null, $clearColl = true) - { - if ($conn == null) { - $conn = $this->_table->getConnection(); - } - - $conn->beginInternalTransaction(); - $conn->transaction->addCollection($this); - - foreach ($this as $key => $record) { - $record->delete($conn); - } - - $conn->commit(); - - if ($clearColl) { - $this->clear(); - } - - return $this; - } - - public function clear() - { - $this->data = array(); - } - - public function free($deep = false) - { - foreach ($this->getData() as $key => $record) { - if ( ! ($record instanceof IPF_ORM_Null)) { - $record->free($deep); - } - } - - $this->data = array(); - - if ($this->reference) { - $this->reference->free($deep); - $this->reference = null; - } - } - - public function getIterator() - { - $data = $this->data; - return new ArrayIterator($data); - } - - public function __toString() - { - return IPF_ORM_Utils::getCollectionAsString($this); - } - - public function getRelation() - { - return $this->relation; - } - - public function __debugInfo() - { - $r = array(); - foreach ($this->data as $item) - $r[] = $item; - return $r; - } -} - diff --git a/ipf/legacy_orm/orm/configurable.php b/ipf/legacy_orm/orm/configurable.php deleted file mode 100644 index a19ad8f..0000000 --- a/ipf/legacy_orm/orm/configurable.php +++ /dev/null @@ -1,180 +0,0 @@ -getAttributeFromString($attribute); - $this->_state = $attribute; - } - - if (is_string($value) && isset($stringAttribute)) { - $value = $this->getAttributeValueFromString($stringAttribute, $value); - } - - switch ($attribute) { - case IPF_ORM::ATTR_COLL_KEY: - if ( ! ($this instanceof IPF_ORM_Table)) { - throw new IPF_ORM_Exception("This attribute can only be set at table level."); - } - if ($value !== null && ! $this->hasField($value)) { - throw new IPF_ORM_Exception("Couldn't set collection key attribute. No such field '$value'."); - } - break; - case IPF_ORM::ATTR_CACHE: - case IPF_ORM::ATTR_RESULT_CACHE: - case IPF_ORM::ATTR_QUERY_CACHE: - if ($value !== null) { - if ( ! ($value instanceof IPF_ORM_Cache_Interface)) { - throw new IPF_ORM_Exception('Cache driver should implement IPF_ORM_Cache_Interface'); - } - } - break; - case IPF_ORM::ATTR_PORTABILITY: - case IPF_ORM::ATTR_DEFAULT_TABLE_TYPE: - case IPF_ORM::ATTR_EMULATE_DATABASE: - case IPF_ORM::ATTR_USE_NATIVE_ENUM: - case IPF_ORM::ATTR_EXPORT: - case IPF_ORM::ATTR_DECIMAL_PLACES: - case IPF_ORM::ATTR_LOAD_REFERENCES: - case IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE: - case IPF_ORM::ATTR_MODEL_LOADING: - case IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN: - case IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN: - case IPF_ORM::ATTR_RECURSIVE_MERGE_FIXTURES; - case IPF_ORM::ATTR_SINGULARIZE_IMPORT; - - break; - case IPF_ORM::ATTR_FIELD_CASE: - if ($value != 0 && $value != CASE_LOWER && $value != CASE_UPPER) - throw new IPF_ORM_Exception('Field case attribute should be either 0, CASE_LOWER or CASE_UPPER constant.'); - break; - default: - throw new IPF_ORM_Exception("Unknown attribute."); - } - - $this->attributes[$attribute] = $value; - } - - public function getParams($namespace = null) - { - if ($namespace == null) { - $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE); - } - - if (!isset($this->_params[$namespace])) { - return null; - } - - return $this->_params[$namespace]; - } - - public function getParamNamespaces() - { - return array_keys($this->_params); - } - - public function setParam($name, $value, $namespace = null) - { - if ($namespace == null) { - $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE); - } - - $this->_params[$namespace][$name] = $value; - - return $this; - } - - public function getParam($name, $namespace = null) - { - if ($namespace == null) { - $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE); - } - - if (!isset($this->_params[$name])) { - if (isset($this->parent)) { - return $this->parent->getParam($name, $namespace); - } - return null; - } - - return $this->_params[$namespace][$name]; - } - - public function getAttribute($attribute) - { - if (is_string($attribute)) { - $upper = strtoupper($attribute); - - $const = 'IPF_ORM::ATTR_' . $upper; - - if (defined($const)) { - $attribute = constant($const); - $this->_state = $attribute; - } else { - throw new IPF_ORM_Exception('Unknown attribute: "' . $attribute . '"'); - } - } - - $attribute = (int) $attribute; - - if ($attribute < 0) { - throw new IPF_ORM_Exception('Unknown attribute.'); - } - - if (isset($this->attributes[$attribute])) { - return $this->attributes[$attribute]; - } - - if (isset($this->parent)) { - return $this->parent->getAttribute($attribute); - } - return null; - } - - public function getAttributes() - { - return $this->attributes; - } - - public function setParent(IPF_ORM_Configurable $component) - { - $this->parent = $component; - } - - public function getParent() - { - return $this->parent; - } -} diff --git a/ipf/legacy_orm/orm/connection.php b/ipf/legacy_orm/orm/connection.php deleted file mode 100644 index 7fddf02..0000000 --- a/ipf/legacy_orm/orm/connection.php +++ /dev/null @@ -1,646 +0,0 @@ - false, - 'expression' => false, - 'export' => false, - 'unitOfWork' => false, - ); - - protected $properties = array('sql_comments' => array(array('start' => '--', 'end' => "\n", 'escape' => false), - array('start' => '/*', 'end' => '*/', 'escape' => false)), - 'identifier_quoting' => '"', - 'string_quoting' => array('start' => "'", - 'end' => "'", - 'escape' => false, - 'escape_pattern' => false), - 'wildcards' => array('%', '_'), - 'varchar_max_length' => 255, - ); - - protected $options = array(); - private static $availableDrivers = array( - 'Mysql', - 'Pgsql', - 'Oracle', - 'Informix', - 'Mssql', - 'Sqlite', - 'Firebird' - ); - protected $_count = 0; - - public $dbListeners = array(); - - public function __construct(IPF_ORM_Manager $manager, $pdo, $user = null, $pass = null) - { - $this->dbh = $pdo; - - $this->setParent($manager); - $this->dbListeners = $manager->dbListeners; - - $this->setAttribute(IPF_ORM::ATTR_CASE, IPF_ORM::CASE_NATURAL); - $this->setAttribute(IPF_ORM::ATTR_ERRMODE, IPF_ORM::ERRMODE_EXCEPTION); - - $this->notifyDBListeners('onOpen', $this); - } - - public function getOptions() - { - return $this->options; - } - - public function getOption($option) - { - if (isset($this->options[$option])) { - return $this->options[$option]; - } - } - - public function setOption($option, $value) - { - return $this->options[$option] = $value; - } - - public function getAttribute($attribute) - { - if (is_string($attribute)) { - $stringAttribute = $attribute; - $attribute = $this->getAttributeFromString($attribute); - } - - if ($attribute >= 100) { - if ( ! isset($this->attributes[$attribute])) { - return parent::getAttribute($attribute); - } - return $this->attributes[$attribute]; - } - - try { - return $this->dbh->getAttribute($attribute); - } catch (Exception $e) { - throw new IPF_ORM_Exception('Attribute ' . $attribute . ' not found.'); - } - } - - public static function getAvailableDrivers() - { - return PDO::getAvailableDrivers(); - } - - public function setAttribute($attribute, $value) - { - if (is_string($attribute)) { - $attributeString = $attribute; - $attribute = parent::getAttributeFromString($attribute); - } - - if (is_string($value) && isset($attributeString)) { - $value = parent::getAttributeValueFromString($attributeString, $value); - } - - if ($attribute >= 100) { - parent::setAttribute($attribute, $value); - } else { - $this->dbh->setAttribute($attribute, $value); - } - - return $this; - } - - public function getName() - { - return $this->_name; - } - - public function getDriverName() - { - return $this->driverName; - } - - public function __get($name) - { - if (isset($this->properties[$name])) { - return $this->properties[$name]; - } - - if ( ! isset($this->modules[$name])) { - throw new IPF_ORM_Exception('Unknown module / property ' . $name); - } - if ($this->modules[$name] === false) { - switch ($name) { - case 'unitOfWork': - $this->modules[$name] = new IPF_ORM_Connection_UnitOfWork($this); - break; - default: - $class = 'IPF_ORM_' . ucwords($name) . '_' . $this->getDriverName(); - $this->modules[$name] = new $class($this); - } - } - - return $this->modules[$name]; - } - - public function getManager() - { - return $this->getParent(); - } - - public function getDbh() - { - return $this->dbh; - } - - public function incrementQueryCount() - { - $this->_count++; - } - - public function replace(IPF_ORM_Table $table, array $fields, array $keys) - { - if (empty($keys)) { - throw new IPF_ORM_Exception('Not specified which fields are keys'); - } - $identifier = (array) $table->getIdentifier(); - $condition = array(); - - foreach ($fields as $fieldName => $value) { - if (in_array($fieldName, $keys)) { - if ($value !== null) { - $condition[] = $table->getColumnName($fieldName) . ' = ?'; - $conditionValues[] = $value; - } - } - } - - $affectedRows = 0; - if ( ! empty($condition) && ! empty($conditionValues)) { - $query = 'DELETE FROM ' . $this->quoteIdentifier($table->getTableName()) - . ' WHERE ' . implode(' AND ', $condition); - - $affectedRows = $this->exec($query, $conditionValues); - } - - $this->insert($table, $fields); - - $affectedRows++; - - return $affectedRows; - } - - public function delete(IPF_ORM_Table $table, array $identifier) - { - $tmp = array(); - - foreach (array_keys($identifier) as $id) { - $tmp[] = $this->quoteIdentifier($table->getColumnName($id)) . ' = ?'; - } - - $query = 'DELETE FROM ' - . $this->quoteIdentifier($table->getTableName()) - . ' WHERE ' . implode(' AND ', $tmp); - - return $this->exec($query, array_values($identifier)); - } - - public function update(IPF_ORM_Table $table, array $fields, array $identifier) - { - if (empty($fields)) { - return false; - } - - $set = array(); - foreach ($fields as $fieldName => $value) { - if ($value instanceof IPF_ORM_Expression) { - $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ' . $value->getSql(); - unset($fields[$fieldName]); - } else { - $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ?'; - } - } - - $params = array_merge(array_values($fields), array_values($identifier)); - - $sql = 'UPDATE ' . $this->quoteIdentifier($table->getTableName()) - . ' SET ' . implode(', ', $set) - . ' WHERE ' . implode(' = ? AND ', $table->getIdentifierColumnNames()) - . ' = ?'; - - return $this->exec($sql, $params); - } - - public function insert(IPF_ORM_Table $table, array $fields) - { - $tableName = $table->getTableName(); - - // column names are specified as array keys - $cols = array(); - // the query VALUES will contain either expresions (eg 'NOW()') or ? - $a = array(); - foreach ($fields as $fieldName => $value) { - $cols[] = $this->quoteIdentifier($table->getColumnName($fieldName)); - if ($value instanceof IPF_ORM_Expression) { - $a[] = $value->getSql(); - unset($fields[$fieldName]); - } else { - $a[] = '?'; - } - } - - // build the statement - $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName) - . ' (' . implode(', ', $cols) . ')' - . ' VALUES (' . implode(', ', $a) . ')'; - - return $this->exec($query, array_values($fields)); - } - - public abstract function quoteIdentifier($str); - - public function convertBooleans($item) - { - if (is_array($item)) { - foreach ($item as $k => $value) { - if (is_bool($value)) { - $item[$k] = (int) $value; - } - } - } else { - if (is_bool($item)) { - $item = (int) $item; - } - } - return $item; - } - - public function quote($input, $type=null) - { - if ($type === null) - $type = gettype($input); - - switch ($type) { - case 'integer': - case 'enum': - case 'boolean': - case 'double': - case 'float': - case 'bool': - case 'decimal': - case 'int': - return $input; - case 'array': - case 'object': - $input = serialize($input); - case 'date': - case 'time': - case 'timestamp': - case 'string': - case 'char': - case 'varchar': - case 'text': - case 'gzip': - case 'blob': - case 'clob': - return $this->getDbh()->quote($input); - default: - throw new IPF_ORM_Exception('Unsupported type \''.$type.'\'.'); - } - } - - public function escapePattern($text) - { - $q = $this->string_quoting['escape_pattern']; - $text = str_replace($q, $q . $q, $text); - foreach ($this->wildcards as $wildcard) { - $text = str_replace($wildcard, $q . $wildcard, $text); - } - return $text; - } - - public function setDateFormat($format = null) - { - } - - public function fetchAll($statement, array $params = array()) - { - return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC); - } - - public function fetchOne($statement, array $params = array(), $colnum = 0) - { - return $this->execute($statement, $params)->fetchColumn($colnum); - } - - public function fetchRow($statement, array $params = array()) - { - return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_ASSOC); - } - - public function fetchArray($statement, array $params = array()) - { - return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_NUM); - } - - public function fetchColumn($statement, array $params = array(), $colnum = 0) - { - return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_COLUMN, $colnum); - } - - public function fetchAssoc($statement, array $params = array()) - { - return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC); - } - - public function fetchBoth($statement, array $params = array()) - { - return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_BOTH); - } - - public function query($query, array $params = array(), $hydrationMode = null) - { - $parser = new IPF_ORM_Query($this); - $res = $parser->query($query, $params, $hydrationMode); - $parser->free(); - - return $res; - } - - public function prepare($statement) - { - try { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_PREPARE, $statement); - - $this->notifyDBListeners('prePrepare', $event); - - $stmt = false; - - if ( ! $event->skipOperation) { - $stmt = $this->dbh->prepare($statement); - } - - $this->notifyDBListeners('postPrepare', $event); - - return new IPF_ORM_Connection_Statement($this, $stmt); - } catch (IPF_ORM_Exception_Adapter $e) { - } catch (PDOException $e) { - } - - $this->rethrowException($e, $this); - } - - public function queryOne($query, array $params = array()) - { - $parser = new IPF_ORM_Query($this); - - $coll = $parser->query($query, $params); - if ( ! $coll->contains(0)) { - return false; - } - return $coll[0]; - } - - public function select($query, $limit = 0, $offset = 0) - { - if ($limit > 0 || $offset > 0) { - $query = $this->modifyLimitQuery($query, $limit, $offset); - } - return $this->execute($query); - } - - public function standaloneQuery($query, $params = array()) - { - return $this->execute($query, $params); - } - - public function execute($query, array $params = array()) - { - try { - if ( ! empty($params)) { - $stmt = $this->prepare($query); - $stmt->execute($params); - - return $stmt; - } else { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_QUERY, $query, $params); - - $this->notifyDBListeners('preQuery', $event); - - if ( ! $event->skipOperation) { - $stmt = $this->dbh->query($query); - $this->_count++; - } - $this->notifyDBListeners('postQuery', $event); - - return $stmt; - } - } catch (IPF_ORM_Exception_Adapter $e) { - } catch (PDOException $e) { - } - - $this->rethrowException($e, $this); - } - - public function exec($query, array $params = array()) - { - try { - if ( ! empty($params)) { - $stmt = $this->prepare($query); - $stmt->execute($params); - - return $stmt->rowCount(); - } else { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_EXEC, $query, $params); - - $this->notifyDBListeners('preExec', $event); - if ( ! $event->skipOperation) { - $count = $this->dbh->exec($query); - - $this->_count++; - } - $this->notifyDBListeners('postExec', $event); - - return $count; - } - } catch (IPF_ORM_Exception_Adapter $e) { - } catch (PDOException $e) { } - - $this->rethrowException($e, $this); - } - - public function rethrowException(Exception $e, $invoker) - { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_ERROR); - $this->notifyDBListeners('onError', $event); - - $name = 'IPF_ORM_Exception_' . $this->driverName; - $exc = new $name($e->getMessage(), (int) $e->getCode()); - if (!is_array($e->errorInfo)) { - $e->errorInfo = array(null, null, null, null); - } - $exc->processErrorInfo($e->errorInfo); - throw $exc; - } - - public function hasTable($name) - { - return isset($this->tables[$name]); - } - - public function getTable($name) - { - if (!isset($this->tables[$name])) - $this->tables[$name] = new IPF_ORM_Table($name, $this); - return $this->tables[$name]; - } - - public function getTables() - { - return $this->tables; - } - - public function getIterator() - { - return new ArrayIterator($this->tables); - } - - public function count() - { - return $this->_count; - } - - public function addTable(IPF_ORM_Table $table) - { - $name = $table->getComponentName(); - - if (isset($this->tables[$name])) { - return false; - } - $this->tables[$name] = $table; - return true; - } - - public function create($name) - { - return $this->getTable($name)->create(); - } - - public function createQuery() - { - return new IPF_ORM_Query($this); - } - - public function flush() - { - $this->beginInternalTransaction(); - $this->unitOfWork->saveAll(); - $this->commit(); - } - - public function clear() - { - foreach ($this->tables as $k => $table) { - $table->getRepository()->evictAll(); - $table->clear(); - } - } - - public function evictTables() - { - $this->tables = array(); - $this->exported = array(); - } - - public function getTransactionLevel() - { - return $this->transaction->getTransactionLevel(); - } - - public function errorCode() - { - return $this->dbh->errorCode(); - } - - public function errorInfo() - { - return $this->dbh->errorInfo(); - } - - public function getCacheDriver() - { - return $this->getResultCacheDriver(); - } - - public function getResultCacheDriver() - { - if ( ! $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE)) { - throw new IPF_ORM_Exception('Result Cache driver not initialized.'); - } - - return $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE); - } - - public function getQueryCacheDriver() - { - if ( ! $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) { - throw new IPF_ORM_Exception('Query Cache driver not initialized.'); - } - - return $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE); - } - - public function lastInsertId() - { - return $this->getDbh()->lastInsertId(); - } - - public function beginTransaction($savepoint = null) - { - return $this->transaction->beginTransaction($savepoint); - } - - public function beginInternalTransaction($savepoint = null) - { - return $this->transaction->beginInternalTransaction($savepoint); - } - - public function commit($savepoint = null) - { - return $this->transaction->commit($savepoint); - } - - public function rollback($savepoint = null) - { - $this->transaction->rollback($savepoint); - } - - public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false) - { - return $query; - } - - public function modifyLimitSubquery(IPF_ORM_Table $rootTable, $query, $limit = false, - $offset = false, $isManip = false) - { - return $this->modifyLimitQuery($query, $limit, $offset, $isManip); - } - - public function __toString() - { - return IPF_ORM_Utils::getConnectionAsString($this); - } - - public function notifyDBListeners($method, $event) - { - foreach ($this->dbListeners as $listener) - if (is_callable(array($listener, $method))) - $listener->$method($event); - } -} - diff --git a/ipf/legacy_orm/orm/connection/module.php b/ipf/legacy_orm/orm/connection/module.php deleted file mode 100644 index b894e2d..0000000 --- a/ipf/legacy_orm/orm/connection/module.php +++ /dev/null @@ -1,29 +0,0 @@ -conn = $conn; - - $e = explode('_', get_class($this)); - - $this->moduleName = $e[1]; - } - - public function getConnection() - { - return $this->conn; - } - - public function getModuleName() - { - return $this->moduleName; - } -} diff --git a/ipf/legacy_orm/orm/connection/mysql.php b/ipf/legacy_orm/orm/connection/mysql.php deleted file mode 100644 index 4195f87..0000000 --- a/ipf/legacy_orm/orm/connection/mysql.php +++ /dev/null @@ -1,124 +0,0 @@ -attributes[IPF_ORM::ATTR_DEFAULT_TABLE_TYPE] = 'INNODB'; - - $this->properties['string_quoting'] = array('start' => "'", - 'end' => "'", - 'escape' => '\\', - 'escape_pattern' => '\\'); - - $this->properties['identifier_quoting'] = '`'; - - $this->properties['sql_comments'] = array( - array('start' => '-- ', 'end' => "\n", 'escape' => false), - array('start' => '#', 'end' => "\n", 'escape' => false), - array('start' => '/*', 'end' => '*/', 'escape' => false), - ); - - $this->properties['varchar_max_length'] = 255; - - parent::__construct($manager, $adapter); - } - - public function quoteIdentifier($str) - { - $quote = $this->identifier_quoting; - $q = array(); - foreach (explode('.', $str) as $s) { - if (in_array(strtoupper($s), self::$keywords)) - $q[] = $quote . str_replace($quote, $quote . $quote, $s) . $quote; - else - $q[] = $s; - } - return implode('.', $q); - } - - public function getDatabaseName() - { - return $this->fetchOne('SELECT DATABASE()'); - } - - public function replace(IPF_ORM_Table $table, array $fields, array $keys) - { - if (empty($keys)) { - throw new IPF_ORM_Exception('Not specified which fields are keys'); - } - $columns = array(); - $values = array(); - $params = array(); - foreach ($fields as $fieldName => $value) { - $columns[] = $table->getColumnName($fieldName); - $values[] = '?'; - $params[] = $value; - } - $query = 'REPLACE INTO ' . $table->getTableName() . ' (' . implode(',', $columns) . ') VALUES (' . implode(',', $values) . ')'; - return $this->exec($query, $params); - } - - public function modifyLimitQuery($query, $limit = false,$offset = false,$isManip=false) - { - $limit = (int) $limit; - $offset = (int) $offset; - - if ($limit && $offset) { - $query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; - } elseif ($limit && ! $offset) { - $query .= ' LIMIT ' . $limit; - } elseif ( ! $limit && $offset) { - $query .= ' LIMIT 999999999999 OFFSET ' . $offset; - } - return $query; - } -} - diff --git a/ipf/legacy_orm/orm/connection/statement.php b/ipf/legacy_orm/orm/connection/statement.php deleted file mode 100644 index cfea7cc..0000000 --- a/ipf/legacy_orm/orm/connection/statement.php +++ /dev/null @@ -1,186 +0,0 @@ -_conn = $conn; - $this->_stmt = $stmt; - - if ($stmt === false) { - throw new IPF_ORM_Exception('Unknown statement object given.'); - } - } - - public function getConnection() - { - return $this->_conn; - } - public function getStatement() - { - return $this->_stmt; - } - public function getQuery() - { - return $this->_stmt->queryString; - } - - public function bindColumn($column, $param, $type = null) - { - if ($type === null) { - return $this->_stmt->bindColumn($column, $param); - } else { - return $this->_stmt->bindColumn($column, $param, $type); - } - } - - public function bindValue($param, $value, $type = null) - { - if ($type === null) { - return $this->_stmt->bindValue($param, $value); - } else { - return $this->_stmt->bindValue($param, $value, $type); - } - } - - public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array()) - { - if ($type === null) { - return $this->_stmt->bindParam($column, $variable); - } else { - return $this->_stmt->bindParam($column, $variable, $type, $length, $driverOptions); - } - } - - public function closeCursor() - { - return $this->_stmt->closeCursor(); - } - - public function columnCount() - { - return $this->_stmt->columnCount(); - } - - public function errorCode() - { - return $this->_stmt->errorCode(); - } - - public function errorInfo() - { - return $this->_stmt->errorInfo(); - } - - public function execute($params = null) - { - try { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_EXECUTE, $this->getQuery(), $params); - $this->_conn->notifyDBListeners('preStmtExecute', $event); - - $result = true; - if ( ! $event->skipOperation) { - $result = $this->_stmt->execute($params); - $this->_conn->incrementQueryCount(); - } - - $this->_conn->notifyDBListeners('postStmtExecute', $event); - - return $result; - } catch (PDOException $e) { - } catch (IPF_ORM_Exception_Adapter $e) { - } - - $this->_conn->rethrowException($e, $this); - - return false; - } - - public function fetch($fetchMode = IPF_ORM::FETCH_BOTH, - $cursorOrientation = IPF_ORM::FETCH_ORI_NEXT, - $cursorOffset = null) - { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCH, $this->getQuery()); - - $event->fetchMode = $fetchMode; - $event->cursorOrientation = $cursorOrientation; - $event->cursorOffset = $cursorOffset; - - $data = $this->_conn->notifyDBListeners('preFetch', $event); - - if ( ! $event->skipOperation) { - $data = $this->_stmt->fetch($fetchMode, $cursorOrientation, $cursorOffset); - } - - $this->_conn->notifyDBListeners('postFetch', $event); - - return $data; - } - - public function fetchAll($fetchMode = IPF_ORM::FETCH_BOTH, - $columnIndex = null) - { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCHALL, $this->getQuery()); - $event->fetchMode = $fetchMode; - $event->columnIndex = $columnIndex; - - $this->_conn->notifyDBListeners('preFetchAll', $event); - - if ( ! $event->skipOperation) { - if ($columnIndex !== null) { - $data = $this->_stmt->fetchAll($fetchMode, $columnIndex); - } else { - $data = $this->_stmt->fetchAll($fetchMode); - } - - $event->data = $data; - } - - $this->_conn->notifyDBListeners('postFetchAll', $event); - - return $data; - } - - public function fetchColumn($columnIndex = 0) - { - return $this->_stmt->fetchColumn($columnIndex); - } - - public function fetchObject($className = 'stdClass', $args = array()) - { - return $this->_stmt->fetchObject($className, $args); - } - - public function getAttribute($attribute) - { - return $this->_stmt->getAttribute($attribute); - } - - public function getColumnMeta($column) - { - return $this->_stmt->getColumnMeta($column); - } - - public function nextRowset() - { - return $this->_stmt->nextRowset(); - } - - public function rowCount() - { - return $this->_stmt->rowCount(); - } - - public function setAttribute($attribute, $value) - { - return $this->_stmt->setAttribute($attribute, $value); - } - - public function setFetchMode($mode, $arg1 = null, $arg2 = null) - { - return $this->_stmt->setFetchMode($mode, $arg1, $arg2); - } -} - diff --git a/ipf/legacy_orm/orm/connection/unitofwork.php b/ipf/legacy_orm/orm/connection/unitofwork.php deleted file mode 100644 index 301b5d7..0000000 --- a/ipf/legacy_orm/orm/connection/unitofwork.php +++ /dev/null @@ -1,574 +0,0 @@ -getConnection(); - - $state = $record->state(); - if ($state === IPF_ORM_Record::STATE_LOCKED) { - return false; - } - - $record->state(IPF_ORM_Record::STATE_LOCKED); - - $conn->beginInternalTransaction(); - $saveLater = $this->saveRelated($record); - - $record->state($state); - - if ($record->isValid()) { - $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE); - $record->preSave($event); - $record->getTable()->notifyRecordListeners('preSave', $event); - $state = $record->state(); - - if ( ! $event->skipOperation) { - switch ($state) { - case IPF_ORM_Record::STATE_TDIRTY: - $this->insert($record); - break; - case IPF_ORM_Record::STATE_DIRTY: - case IPF_ORM_Record::STATE_PROXY: - $this->update($record); - break; - case IPF_ORM_Record::STATE_CLEAN: - case IPF_ORM_Record::STATE_TCLEAN: - break; - } - } - - // NOTE: what about referential integrity issues? - foreach ($record->getPendingDeletes() as $pendingDelete) { - $pendingDelete->delete(); - } - - $record->getTable()->notifyRecordListeners('postSave', $event); - $record->postSave($event); - } else { - $conn->transaction->addInvalid($record); - } - - $state = $record->state(); - - $record->state(IPF_ORM_Record::STATE_LOCKED); - - foreach ($saveLater as $fk) { - $alias = $fk->getAlias(); - - if ($record->hasReference($alias)) { - $obj = $record->$alias; - // check that the related object is not an instance of IPF_ORM_Null - if ( ! ($obj instanceof IPF_ORM_Null)) { - $obj->save($conn); - } - } - } - - // save the MANY-TO-MANY associations - $this->saveAssociations($record); - - $record->state($state); - $conn->commit(); - - return true; - } - - public function save(IPF_ORM_Record $record) - { - $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE); - - $record->preSave($event); - - $record->getTable()->notifyRecordListeners('preSave', $event); - - if ( ! $event->skipOperation) { - switch ($record->state()) { - case IPF_ORM_Record::STATE_TDIRTY: - $this->insert($record); - break; - case IPF_ORM_Record::STATE_DIRTY: - case IPF_ORM_Record::STATE_PROXY: - $this->update($record); - break; - case IPF_ORM_Record::STATE_CLEAN: - case IPF_ORM_Record::STATE_TCLEAN: - // do nothing - break; - } - } - - $record->getTable()->notifyRecordListeners('postSave', $event); - - $record->postSave($event); - } - - public function delete(IPF_ORM_Record $record) - { - $deletions = array(); - $this->_collectDeletions($record, $deletions); - return $this->_executeDeletions($deletions); - } - - private function _collectDeletions(IPF_ORM_Record $record, array &$deletions) - { - if ( ! $record->exists()) { - return; - } - - $deletions[$record->getOid()] = $record; - $this->_cascadeDelete($record, $deletions); - } - - private function _executeDeletions(array $deletions) - { - // collect class names - $classNames = array(); - foreach ($deletions as $record) { - $classNames[] = $record->getTable()->getComponentName(); - } - $classNames = array_unique($classNames); - - // order deletes - $executionOrder = $this->buildFlushTree($classNames); - - // execute - try { - $this->conn->beginInternalTransaction(); - - for ($i = count($executionOrder) - 1; $i >= 0; $i--) { - $className = $executionOrder[$i]; - $table = $this->conn->getTable($className); - - // collect identifiers - $identifierMaps = array(); - $deletedRecords = array(); - foreach ($deletions as $oid => $record) { - if ($record->getTable()->getComponentName() == $className) { - $veto = $this->_preDelete($record); - if ( ! $veto) { - $identifierMaps[] = $record->identifier(); - $deletedRecords[] = $record; - unset($deletions[$oid]); - } - } - } - - if (count($deletedRecords) < 1) { - continue; - } - - // extract query parameters (only the identifier values are of interest) - $params = array(); - $columnNames = array(); - foreach ($identifierMaps as $idMap) { - while (list($fieldName, $value) = each($idMap)) { - $params[] = $value; - $columnNames[] = $table->getColumnName($fieldName); - } - } - $columnNames = array_unique($columnNames); - - // delete - $tableName = $table->getTableName(); - $sql = "DELETE FROM " . $this->conn->quoteIdentifier($tableName) . " WHERE "; - - if ($table->isIdentifierComposite()) { - $sql .= $this->_buildSqlCompositeKeyCondition($columnNames, count($identifierMaps)); - $this->conn->exec($sql, $params); - } else { - $sql .= $this->_buildSqlSingleKeyCondition($columnNames, count($params)); - $this->conn->exec($sql, $params); - } - - // adjust state, remove from identity map and inform postDelete listeners - foreach ($deletedRecords as $record) { - $record->state(IPF_ORM_Record::STATE_TCLEAN); - $record->getTable()->removeRecord($record); - $this->_postDelete($record); - } - } - - $this->conn->commit(); - // trigger postDelete for records skipped during the deletion (veto!) - foreach ($deletions as $skippedRecord) { - $this->_postDelete($skippedRecord); - } - - return true; - } catch (Exception $e) { - $this->conn->rollback(); - throw $e; - } - } - - private function _buildSqlSingleKeyCondition($columnNames, $numRecords) - { - $idColumn = $this->conn->quoteIdentifier($columnNames[0]); - return implode(' OR ', array_fill(0, $numRecords, "$idColumn = ?")); - } - - private function _buildSqlCompositeKeyCondition($columnNames, $numRecords) - { - $singleCondition = ""; - foreach ($columnNames as $columnName) { - $columnName = $this->conn->quoteIdentifier($columnName); - if ($singleCondition === "") { - $singleCondition .= "($columnName = ?"; - } else { - $singleCondition .= " AND $columnName = ?"; - } - } - $singleCondition .= ")"; - $fullCondition = implode(' OR ', array_fill(0, $numRecords, $singleCondition)); - - return $fullCondition; - } - - protected function _cascadeDelete(IPF_ORM_Record $record, array &$deletions) - { - foreach ($record->getTable()->getRelations() as $relation) { - if ($relation->isCascadeDelete()) { - $fieldName = $relation->getAlias(); - // if it's a xToOne relation and the related object is already loaded - // we don't need to refresh. - if ( ! ($relation->getType() == IPF_ORM_Relation::ONE && isset($record->$fieldName))) { - $record->refreshRelated($relation->getAlias()); - } - $relatedObjects = $record->get($relation->getAlias()); - if ($relatedObjects instanceof IPF_ORM_Record && $relatedObjects->exists() - && ! isset($deletions[$relatedObjects->getOid()])) { - $this->_collectDeletions($relatedObjects, $deletions); - } else if ($relatedObjects instanceof IPF_ORM_Collection && count($relatedObjects) > 0) { - // cascade the delete to the other objects - foreach ($relatedObjects as $object) { - if ( ! isset($deletions[$object->getOid()])) { - $this->_collectDeletions($object, $deletions); - } - } - } - } - } - } - - public function saveRelated(IPF_ORM_Record $record) - { - $saveLater = array(); - foreach ($record->getReferences() as $k => $v) { - $rel = $record->getTable()->getRelation($k); - - $local = $rel->getLocal(); - $foreign = $rel->getForeign(); - - if ($rel instanceof IPF_ORM_Relation_ForeignKey) { - $saveLater[$k] = $rel; - } else if ($rel instanceof IPF_ORM_Relation_LocalKey) { - // ONE-TO-ONE relationship - $obj = $record->get($rel->getAlias()); - - // Protection against infinite function recursion before attempting to save - if ($obj instanceof IPF_ORM_Record && $obj->isModified()) { - $obj->save($this->conn); - - /** Can this be removed? - $id = array_values($obj->identifier()); - - foreach ((array) $rel->getLocal() as $k => $field) { - $record->set($field, $id[$k]); - } - */ - } - } - } - - return $saveLater; - } - - public function saveAssociations(IPF_ORM_Record $record) - { - foreach ($record->getReferences() as $k => $v) { - $rel = $record->getTable()->getRelation($k); - //print get_class($rel); - if ($rel instanceof IPF_ORM_Relation_Association) { - $v->save($this->conn); - - $assocTable = $rel->getAssociationTable(); - - foreach ($v->getDeleteDiff() as $r) { - $query = 'DELETE FROM ' . $assocTable->getTableName() - . ' WHERE ' . $rel->getForeign() . ' = ?' - . ' AND ' . $rel->getLocal() . ' = ?'; - - $this->conn->execute($query, array($r->getIncremented(), $record->getIncremented())); - } - - foreach ($v->getInsertDiff() as $r) { - $assocRecord = $assocTable->create(); - $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r); - $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record); - $this->saveGraph($assocRecord); - } - } - } - } - - private function _preDelete(IPF_ORM_Record $record) - { - $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE); - $record->preDelete($event); - $record->getTable()->notifyRecordListeners('preDelete', $event); - - return $event->skipOperation; - } - - private function _postDelete(IPF_ORM_Record $record) - { - $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE); - $record->postDelete($event); - $record->getTable()->notifyRecordListeners('postDelete', $event); - } - - public function saveAll() - { - // get the flush tree - $tree = $this->buildFlushTree($this->conn->getTables()); - - // save all records - foreach ($tree as $name) { - $table = $this->conn->getTable($name); - foreach ($table->getRepository() as $record) { - $this->saveGraph($record); - } - } - } - - public function update(IPF_ORM_Record $record) - { - $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_UPDATE); - $record->preUpdate($event); - $table = $record->getTable(); - $table->notifyRecordListeners('preUpdate', $event); - - if ( ! $event->skipOperation) { - $identifier = $record->identifier(); - $array = $record->getPrepared(); - $this->conn->update($table, $array, $identifier); - $record->assignIdentifier(true); - } - - $table->notifyRecordListeners('postUpdate', $event); - - $record->postUpdate($event); - - return true; - } - - public function insert(IPF_ORM_Record $record) - { - // listen the onPreInsert event - $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_INSERT); - $record->preInsert($event); - $table = $record->getTable(); - $table->notifyRecordListeners('preInsert', $event); - - if ( ! $event->skipOperation) { - $this->processSingleInsert($record); - } - - $table->addRecord($record); - $table->notifyRecordListeners('postInsert', $event); - $record->postInsert($event); - - return true; - } - - public function processSingleInsert(IPF_ORM_Record $record) - { - $fields = $record->getPrepared(); - $table = $record->getTable(); - - // Populate fields with a blank array so that a blank records can be inserted - if (empty($fields)) { - foreach ($table->getFieldNames() as $field) { - $fields[$field] = null; - } - } - - $identifier = (array) $table->getIdentifier(); - - $this->conn->insert($table, $fields); - - if (count($identifier) == 1 && $table->getIdentifierType() !== IPF_ORM::IDENTIFIER_NATURAL) { - $id = $this->conn->lastInsertId(); - if (!$id) - throw new IPF_ORM_Exception("Couldn't get last insert identifier."); - - $record->assignIdentifier($id); - } else { - $record->assignIdentifier(true); - } - } - - public function buildFlushTree(array $tables) - { - // determine classes to order. only necessary because the $tables param - // can contain strings or table objects... - $classesToOrder = array(); - foreach ($tables as $table) { - if ( ! ($table instanceof IPF_ORM_Table)) { - $table = $this->conn->getTable($table, false); - } - $classesToOrder[] = $table->getComponentName(); - } - $classesToOrder = array_unique($classesToOrder); - - if (count($classesToOrder) < 2) { - return $classesToOrder; - } - - // build the correct order - $flushList = array(); - foreach ($classesToOrder as $class) { - $table = $this->conn->getTable($class, false); - $currentClass = $table->getComponentName(); - - $index = array_search($currentClass, $flushList); - - if ($index === false) { - //echo "adding $currentClass to flushlist"; - $flushList[] = $currentClass; - $index = max(array_keys($flushList)); - } - - $rels = $table->getRelations(); - - // move all foreignkey relations to the beginning - foreach ($rels as $key => $rel) { - if ($rel instanceof IPF_ORM_Relation_ForeignKey) { - unset($rels[$key]); - array_unshift($rels, $rel); - } - } - - foreach ($rels as $rel) { - $relatedClassName = $rel->getTable()->getComponentName(); - - if ( ! in_array($relatedClassName, $classesToOrder)) { - continue; - } - - $relatedCompIndex = array_search($relatedClassName, $flushList); - $type = $rel->getType(); - - // skip self-referenced relations - if ($relatedClassName === $currentClass) { - continue; - } - - if ($rel instanceof IPF_ORM_Relation_ForeignKey) { - // the related component needs to come after this component in - // the list (since it holds the fk) - - if ($relatedCompIndex !== false) { - // the component is already in the list - if ($relatedCompIndex >= $index) { - // it's already in the right place - continue; - } - - unset($flushList[$index]); - // the related comp has the fk. so put "this" comp immediately - // before it in the list - array_splice($flushList, $relatedCompIndex, 0, $currentClass); - $index = $relatedCompIndex; - } else { - $flushList[] = $relatedClassName; - } - - } else if ($rel instanceof IPF_ORM_Relation_LocalKey) { - // the related component needs to come before the current component - // in the list (since this component holds the fk). - - if ($relatedCompIndex !== false) { - // already in flush list - if ($relatedCompIndex <= $index) { - // it's in the right place - continue; - } - - unset($flushList[$relatedCompIndex]); - // "this" comp has the fk. so put the related comp before it - // in the list - array_splice($flushList, $index, 0, $relatedClassName); - } else { - array_unshift($flushList, $relatedClassName); - $index++; - } - } else if ($rel instanceof IPF_ORM_Relation_Association) { - // the association class needs to come after both classes - // that are connected through it in the list (since it holds - // both fks) - - $assocTable = $rel->getAssociationFactory(); - $assocClassName = $assocTable->getComponentName(); - - if ($relatedCompIndex !== false) { - unset($flushList[$relatedCompIndex]); - } - - array_splice($flushList, $index, 0, $relatedClassName); - $index++; - - $index3 = array_search($assocClassName, $flushList); - - if ($index3 !== false) { - if ($index3 >= $index) { - continue; - } - - unset($flushList[$index]); - array_splice($flushList, $index3, 0, $assocClassName); - $index = $relatedCompIndex; - } else { - $flushList[] = $assocClassName; - } - } - } - } - - return array_values($flushList); - } - - private function _formatDataSet(IPF_ORM_Record $record) - { - $table = $record->getTable(); - $dataSet = array(); - $component = $table->getComponentName(); - $array = $record->getPrepared(); - - foreach ($table->getColumns() as $columnName => $definition) { - if ( ! isset($dataSet[$component])) { - $dataSet[$component] = array(); - } - - $fieldName = $table->getFieldName($columnName); - if (isset($definition['primary']) && $definition['primary']) { - continue; - } - - if ( ! array_key_exists($fieldName, $array)) { - continue; - } - - if (isset($definition['owner'])) { - $dataSet[$definition['owner']][$fieldName] = $array[$fieldName]; - } else { - $dataSet[$component][$fieldName] = $array[$fieldName]; - } - } - - return $dataSet; - } -} - diff --git a/ipf/legacy_orm/orm/event.php b/ipf/legacy_orm/orm/event.php deleted file mode 100644 index c52fd29..0000000 --- a/ipf/legacy_orm/orm/event.php +++ /dev/null @@ -1,194 +0,0 @@ -_invoker = $invoker; - $this->_code = $code; - $this->_query = $query; - $this->_params = $params; - } - - public function getQuery() - { - return $this->_query; - } - - public function getName() - { - switch ($this->_code) { - case self::CONN_QUERY: - return 'query'; - case self::CONN_EXEC: - return 'exec'; - case self::CONN_PREPARE: - return 'prepare'; - case self::CONN_CONNECT: - return 'connect'; - case self::CONN_CLOSE: - return 'close'; - case self::CONN_ERROR: - return 'error'; - - case self::STMT_EXECUTE: - return 'execute'; - case self::STMT_FETCH: - return 'fetch'; - case self::STMT_FETCHALL: - return 'fetch all'; - - case self::TX_BEGIN: - return 'begin'; - case self::TX_COMMIT: - return 'commit'; - case self::TX_ROLLBACK: - return 'rollback'; - - case self::SAVEPOINT_CREATE: - return 'create savepoint'; - case self::SAVEPOINT_ROLLBACK: - return 'rollback savepoint'; - case self::SAVEPOINT_COMMIT: - return 'commit savepoint'; - - case self::RECORD_DELETE: - return 'delete record'; - case self::RECORD_SAVE: - return 'save record'; - case self::RECORD_UPDATE: - return 'update record'; - case self::RECORD_INSERT: - return 'insert record'; - case self::RECORD_SERIALIZE: - return 'serialize record'; - case self::RECORD_UNSERIALIZE: - return 'unserialize record'; - case self::RECORD_DQL_SELECT: - return 'select records'; - case self::RECORD_DQL_DELETE: - return 'delete records'; - case self::RECORD_DQL_UPDATE: - return 'update records'; - } - } - - public function getCode() - { - return $this->_code; - } - - public function __get($option) - { - if ( ! isset($this->_options[$option])) { - return null; - } - - return $this->_options[$option]; - } - - public function skipOperation() - { - $this->_options['skipOperation'] = true; - - return $this; - } - - public function __set($option, $value) - { - $this->_options[$option] = $value; - - return $this; - } - - public function set($option, &$value) - { - $this->_options[$option] =& $value; - - return $this; - } - - public function start() - { - $this->_startedMicrotime = microtime(true); - } - - public function hasEnded() - { - return ($this->_endedMicrotime != null); - } - - public function end() - { - $this->_endedMicrotime = microtime(true); - - return $this; - } - - public function getInvoker() - { - return $this->_invoker; - } - - public function setInvoker($invoker) - { - $this->_invoker = $invoker; - } - - public function getParams() - { - return $this->_params; - } - - public function getElapsedSecs() - { - if (is_null($this->_endedMicrotime)) { - return false; - } - return ($this->_endedMicrotime - $this->_startedMicrotime); - } -} diff --git a/ipf/legacy_orm/orm/exception.php b/ipf/legacy_orm/orm/exception.php deleted file mode 100644 index d7d84e9..0000000 --- a/ipf/legacy_orm/orm/exception.php +++ /dev/null @@ -1,50 +0,0 @@ - 'unknown error', - IPF_ORM::ERR_ALREADY_EXISTS => 'already exists', - IPF_ORM::ERR_CANNOT_CREATE => 'can not create', - IPF_ORM::ERR_CANNOT_ALTER => 'can not alter', - IPF_ORM::ERR_CANNOT_REPLACE => 'can not replace', - IPF_ORM::ERR_CANNOT_DELETE => 'can not delete', - IPF_ORM::ERR_CANNOT_DROP => 'can not drop', - IPF_ORM::ERR_CONSTRAINT => 'constraint violation', - IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', - IPF_ORM::ERR_DIVZERO => 'division by zero', - IPF_ORM::ERR_INVALID => 'invalid', - IPF_ORM::ERR_INVALID_DATE => 'invalid date or time', - IPF_ORM::ERR_INVALID_NUMBER => 'invalid number', - IPF_ORM::ERR_MISMATCH => 'mismatch', - IPF_ORM::ERR_NODBSELECTED => 'no database selected', - IPF_ORM::ERR_NOSUCHFIELD => 'no such field', - IPF_ORM::ERR_NOSUCHTABLE => 'no such table', - IPF_ORM::ERR_NOT_CAPABLE => 'IPF backend not capable', - IPF_ORM::ERR_NOT_FOUND => 'not found', - IPF_ORM::ERR_NOT_LOCKED => 'not locked', - IPF_ORM::ERR_SYNTAX => 'syntax error', - IPF_ORM::ERR_UNSUPPORTED => 'not supported', - IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row', - IPF_ORM::ERR_INVALID_DSN => 'invalid DSN', - IPF_ORM::ERR_CONNECT_FAILED => 'connect failed', - IPF_ORM::ERR_NEED_MORE_DATA => 'insufficient data supplied', - IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found', - IPF_ORM::ERR_NOSUCHDB => 'no such database', - IPF_ORM::ERR_ACCESS_VIOLATION => 'insufficient permissions', - IPF_ORM::ERR_LOADMODULE => 'error while including on demand module', - IPF_ORM::ERR_TRUNCATED => 'truncated', - IPF_ORM::ERR_DEADLOCK => 'deadlock detected', - ); - - public function errorMessage($value = null) - { - if (is_null($value)) { - return self::$_errorMessages; - } - - return isset(self::$_errorMessages[$value]) ? - self::$_errorMessages[$value] : self::$_errorMessages[IPF_ORM::ERR]; - } -} - diff --git a/ipf/legacy_orm/orm/exception/adapter.php b/ipf/legacy_orm/orm/exception/adapter.php deleted file mode 100644 index 7f8ce00..0000000 --- a/ipf/legacy_orm/orm/exception/adapter.php +++ /dev/null @@ -1,3 +0,0 @@ - 'unknown error', - IPF_ORM::ERR_ALREADY_EXISTS => 'already exists', - IPF_ORM::ERR_CANNOT_CREATE => 'can not create', - IPF_ORM::ERR_CANNOT_ALTER => 'can not alter', - IPF_ORM::ERR_CANNOT_REPLACE => 'can not replace', - IPF_ORM::ERR_CANNOT_DELETE => 'can not delete', - IPF_ORM::ERR_CANNOT_DROP => 'can not drop', - IPF_ORM::ERR_CONSTRAINT => 'constraint violation', - IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', - IPF_ORM::ERR_DIVZERO => 'division by zero', - IPF_ORM::ERR_INVALID => 'invalid', - IPF_ORM::ERR_INVALID_DATE => 'invalid date or time', - IPF_ORM::ERR_INVALID_NUMBER => 'invalid number', - IPF_ORM::ERR_MISMATCH => 'mismatch', - IPF_ORM::ERR_NODBSELECTED => 'no database selected', - IPF_ORM::ERR_NOSUCHFIELD => 'no such field', - IPF_ORM::ERR_NOSUCHTABLE => 'no such table', - IPF_ORM::ERR_NOT_CAPABLE => 'IPF_ORM backend not capable', - IPF_ORM::ERR_NOT_FOUND => 'not found', - IPF_ORM::ERR_NOT_LOCKED => 'not locked', - IPF_ORM::ERR_SYNTAX => 'syntax error', - IPF_ORM::ERR_UNSUPPORTED => 'not supported', - IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row', - IPF_ORM::ERR_INVALID_DSN => 'invalid DSN', - IPF_ORM::ERR_CONNECT_FAILED => 'connect failed', - IPF_ORM::ERR_NEED_MORE_DATA => 'insufficient data supplied', - IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found', - IPF_ORM::ERR_NOSUCHDB => 'no such database', - IPF_ORM::ERR_ACCESS_VIOLATION => 'insufficient permissions', - IPF_ORM::ERR_LOADMODULE => 'error while including on demand module', - IPF_ORM::ERR_TRUNCATED => 'truncated', - IPF_ORM::ERR_DEADLOCK => 'deadlock detected', - ); - - protected $portableCode; - - public function getPortableCode() - { - return $this->portableCode; - } - - public function getPortableMessage() - { - return self::errorMessage($this->portableCode); - } - - public function errorMessage($value = null) - { - return isset(self::$errorMessages[$value]) ? - self::$errorMessages[$value] : self::$errorMessages[IPF_ORM::ERR]; - } - - public function processErrorInfo(array $errorInfo) - { } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/exception/locator.php b/ipf/legacy_orm/orm/exception/locator.php deleted file mode 100644 index d39308f..0000000 --- a/ipf/legacy_orm/orm/exception/locator.php +++ /dev/null @@ -1,3 +0,0 @@ - IPF_ORM::ERR_CANNOT_CREATE, - 1005 => IPF_ORM::ERR_CANNOT_CREATE, - 1006 => IPF_ORM::ERR_CANNOT_CREATE, - 1007 => IPF_ORM::ERR_ALREADY_EXISTS, - 1008 => IPF_ORM::ERR_CANNOT_DROP, - 1022 => IPF_ORM::ERR_ALREADY_EXISTS, - 1044 => IPF_ORM::ERR_ACCESS_VIOLATION, - 1046 => IPF_ORM::ERR_NODBSELECTED, - 1048 => IPF_ORM::ERR_CONSTRAINT, - 1049 => IPF_ORM::ERR_NOSUCHDB, - 1050 => IPF_ORM::ERR_ALREADY_EXISTS, - 1051 => IPF_ORM::ERR_NOSUCHTABLE, - 1054 => IPF_ORM::ERR_NOSUCHFIELD, - 1061 => IPF_ORM::ERR_ALREADY_EXISTS, - 1062 => IPF_ORM::ERR_ALREADY_EXISTS, - 1064 => IPF_ORM::ERR_SYNTAX, - 1091 => IPF_ORM::ERR_NOT_FOUND, - 1100 => IPF_ORM::ERR_NOT_LOCKED, - 1136 => IPF_ORM::ERR_VALUE_COUNT_ON_ROW, - 1142 => IPF_ORM::ERR_ACCESS_VIOLATION, - 1146 => IPF_ORM::ERR_NOSUCHTABLE, - 1216 => IPF_ORM::ERR_CONSTRAINT, - 1217 => IPF_ORM::ERR_CONSTRAINT, - 1451 => IPF_ORM::ERR_CONSTRAINT, - ); - public function processErrorInfo(array $errorInfo) - { - $code = $errorInfo[1]; - if (isset(self::$errorCodeMap[$code])) { - $this->portableCode = self::$errorCodeMap[$code]; - return true; - } - return false; - } -} diff --git a/ipf/legacy_orm/orm/exception/profiler.php b/ipf/legacy_orm/orm/exception/profiler.php deleted file mode 100644 index 5fa9ad5..0000000 --- a/ipf/legacy_orm/orm/exception/profiler.php +++ /dev/null @@ -1,3 +0,0 @@ -invalid = $invalid; - parent::__construct($this->generateMessage()); - } - - public function getInvalidRecords() - { - return $this->invalid; - } - - private function generateMessage() - { - $message = ""; - foreach ($this->invalid as $record) { - $errors = array(); - foreach ($record->getErrors() as $field => $validators) - $errors[] = 'Field "' . $field . '" failed following validators: ' . implode(', ', $validators) . '.'; - $message .= "Validaton error in class " . get_class($record) . ' (' . implode(' ', $errors) . ') '; - } - return $message; - } -} - diff --git a/ipf/legacy_orm/orm/export.php b/ipf/legacy_orm/orm/export.php deleted file mode 100644 index a0cae94..0000000 --- a/ipf/legacy_orm/orm/export.php +++ /dev/null @@ -1,479 +0,0 @@ - '', - 'boolean' => true, - 'integer' => 0, - 'decimal' => 0.0, - 'float' => 0.0, - 'double' => 0.0, - 'timestamp' => '1970-01-01 00:00:00', - 'time' => '00:00:00', - 'date' => '1970-01-01', - 'clob' => '', - 'blob' => '', - 'string' => '' - ); - - protected function getIndexName($name) - { - return $name . '_idx'; - } - - public function dropTableSql($table) - { - return 'DROP TABLE ' . $this->conn->quoteIdentifier($table); - } - - public function dropTable($table) - { - $this->conn->execute($this->dropTableSql($table)); - } - - public function dropIndex($table, $name) - { - return $this->conn->exec($this->dropIndexSql($table, $name)); - } - - public function dropIndexSql($table, $name) - { - $name = $this->conn->quoteIdentifier($this->getIndexName($name)); - - return 'DROP INDEX ' . $name; - } - - public function dropConstraint($table, $name, $primary = false) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($name); - - return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name); - } - - public function dropForeignKey($table, $name) - { - return $this->dropConstraint($table, $name); - } - - public function createConstraint($table, $name, $definition) - { - $sql = $this->createConstraintSql($table, $name, $definition); - - return $this->conn->exec($sql); - } - - public function createConstraintSql($table, $name, $definition) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($this->getIndexName($name)); - $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name; - - if (isset($definition['primary']) && $definition['primary']) { - $query .= ' PRIMARY KEY'; - } elseif (isset($definition['unique']) && $definition['unique']) { - $query .= ' UNIQUE'; - } - - $fields = array(); - foreach (array_keys($definition['fields']) as $field) { - $fields[] = $this->conn->quoteIdentifier($field); - } - $query .= ' ('. implode(', ', $fields) . ')'; - - return $query; - } - - public function createIndex($table, $name, array $definition) - { - return $this->conn->execute($this->createIndexSql($table, $name, $definition)); - } - - public function createIndexSql($table, $name, array $definition) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($name); - $type = ''; - - if (isset($definition['type'])) { - switch (strtolower($definition['type'])) { - case 'unique': - $type = strtoupper($definition['type']) . ' '; - break; - default: - throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); - } - } - - $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; - - $fields = array(); - foreach ($definition['fields'] as $field) { - $fields[] = $this->conn->quoteIdentifier($field); - } - $query .= ' (' . implode(', ', $fields) . ')'; - - return $query; - } - - public function createForeignKeySql($table, array $definition) - { - $table = $this->conn->quoteIdentifier($table); - - $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclaration($definition); - - return $query; - } - - public function createForeignKey($table, array $definition) - { - $sql = $this->createForeignKeySql($table, $definition); - - return $this->conn->execute($sql); - } - - public function alterTable($name, array $changes, $check = false) - { - $sql = $this->alterTableSql($name, $changes, $check); - - if (is_string($sql) && $sql) { - $this->conn->execute($sql); - } - } - - public function alterTableSql($name, array $changes, $check = false) - { - throw new IPF_ORM_Exception('Alter table not supported by this driver.'); - } - - public function getFieldDeclarationList(array $fields) - { - foreach ($fields as $fieldName => $field) { - $query = $this->getDeclaration($fieldName, $field); - - $queryFields[] = $query; - } - return implode(', ', $queryFields); - } - - public function getCheckDeclaration(array $definition) - { - $constraints = array(); - foreach ($definition as $field => $def) { - if (is_string($def)) { - $constraints[] = 'CHECK (' . $def . ')'; - } else { - if (isset($def['min'])) { - $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')'; - } - - if (isset($def['max'])) { - $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')'; - } - } - } - - return implode(', ', $constraints); - } - - public function getIndexDeclaration($name, array $definition) - { - $name = $this->conn->quoteIdentifier($name); - $type = ''; - - if (isset($definition['type'])) { - if (strtolower($definition['type']) == 'unique') { - $type = strtoupper($definition['type']) . ' '; - } else { - throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); - } - } - - if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) { - throw new IPF_ORM_Exception('No index columns given.'); - } - - $query = $type . 'INDEX ' . $name; - - $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; - - return $query; - } - - public function getIndexFieldDeclarationList(array $fields) - { - $ret = array(); - foreach ($fields as $field => $definition) { - if (is_array($definition)) { - $ret[] = $this->conn->quoteIdentifier($field); - } else { - $ret[] = $this->conn->quoteIdentifier($definition); - } - } - return implode(', ', $ret); - } - - public function getTemporaryTableQuery() - { - return 'TEMPORARY'; - } - - public function getForeignKeyDeclaration(array $definition) - { - $sql = $this->getForeignKeyBaseDeclaration($definition); - $sql .= $this->getAdvancedForeignKeyOptions($definition); - return $sql; - } - - public function getAdvancedForeignKeyOptions(array $definition) - { - $query = ''; - if ( ! empty($definition['onUpdate'])) { - $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']); - } - if ( ! empty($definition['onDelete'])) { - $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']); - } - return $query; - } - - public function getForeignKeyReferentialAction($action) - { - $upper = strtoupper($action); - switch ($upper) { - case 'CASCADE': - case 'SET NULL': - case 'NO ACTION': - case 'RESTRICT': - case 'SET DEFAULT': - return $upper; - break; - default: - throw new IPF_ORM_Exception('Unknown foreign key referential action \'' . $upper . '\' given.'); - } - } - - public function getForeignKeyBaseDeclaration(array $definition) - { - $sql = ''; - if (isset($definition['name'])) { - $sql .= ' CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' '; - } - $sql .= 'FOREIGN KEY ('; - - if ( ! isset($definition['local'])) { - throw new IPF_ORM_Exception('Local reference field missing from definition.'); - } - if ( ! isset($definition['foreign'])) { - throw new IPF_ORM_Exception('Foreign reference field missing from definition.'); - } - if ( ! isset($definition['foreignTable'])) { - throw new IPF_ORM_Exception('Foreign reference table missing from definition.'); - } - - if ( ! is_array($definition['local'])) { - $definition['local'] = array($definition['local']); - } - if ( ! is_array($definition['foreign'])) { - $definition['foreign'] = array($definition['foreign']); - } - - $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local'])) - . ') REFERENCES ' - . $this->conn->quoteIdentifier($definition['foreignTable']) . '(' - . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')'; - - return $sql; - } - - public function exportSortedClassesSql($classes, $groupByConnection = true) - { - $connections = array(); - foreach ($classes as $class) { - $connection = IPF_ORM_Manager::connection(); - $connectionName = $connection->getName(); - - if ( ! isset($connections[$connectionName])) { - $connections[$connectionName] = array( - 'create_tables' => array(), - 'create_indexes' => array(), - 'alters' => array() - ); - } - - $sql = $this->exportClassesSql($class); - - // Build array of all the creates - // We need these to happen first - foreach ($sql as $key => $query) { - // If create table statement - if (substr($query, 0, strlen('CREATE TABLE')) == 'CREATE TABLE') { - $connections[$connectionName]['create_tables'][] = $query; - - unset($sql[$key]); - continue; - } - - // If create index statement - if (preg_grep("/CREATE .* INDEX/", array($query))) { - $connections[$connectionName]['create_indexes'][] = $query; - - unset($sql[$key]); - continue; - } - - // If alter table statement - if (substr($query, 0, strlen('ALTER TABLE')) == 'ALTER TABLE') { - $connections[$connectionName]['alters'][] = $query; - - unset($sql[$key]); - continue; - } - } - } - - // Loop over all the sql again to merge everything together so it is in the correct order - $build = array(); - foreach ($connections as $connectionName => $sql) { - $build[$connectionName] = array_merge($sql['create_tables'], $sql['create_indexes'], $sql['alters']); - } - - if ( ! $groupByConnection) { - $new = array(); - foreach($build as $connectionname => $sql) { - $new = array_merge($new, $sql); - } - $build = $new; - } - return $build; - } - - public function exportClasses(array $classes) - { - $queries = $this->exportSortedClassesSql($classes); - - foreach ($queries as $connectionName => $sql) { - $connection = IPF_ORM_Manager::connection(); - - $connection->beginTransaction(); - - foreach ($sql as $query) { - try { - $connection->exec($query); - } catch (IPF_ORM_Exception $e) { - // we only want to silence table already exists errors - if ($e->getPortableCode() !== IPF_ORM::ERR_ALREADY_EXISTS) { - $connection->rollback(); - throw new IPF_ORM_Exception($e->getMessage() . '. Failing Query: ' . $query); - } - } - } - $connection->commit(); - } - } - - public function exportClassesSql($name) - { - $sql = array(); - - $table = IPF_ORM::getTable($name); - - $data = $this->getExportableFormat($table); - - $query = $this->createTableSql($data['tableName'], $data['columns'], $data['options']); - - if (is_array($query)) { - $sql = array_merge($sql, $query); - } else { - $sql[] = $query; - } - - $sql = array_unique($sql); - - rsort($sql); - - return $sql; - } - - private function getExportableFormat($table) - { - $columns = array(); - $primary = array(); - - foreach ($table->getColumns() as $name => $definition) { - - if (isset($definition['owner'])) { - continue; - } - - switch ($definition['type']) { - case 'enum': - if (isset($definition['default'])) { - $definition['default'] = $table->enumIndex($name, $definition['default']); - } - break; - case 'boolean': - if (isset($definition['default'])) { - $definition['default'] = $table->getConnection()->convertBooleans($definition['default']); - } - break; - } - $columns[$name] = $definition; - - if (isset($definition['primary']) && $definition['primary']) { - $primary[] = $name; - } - } - - $options['foreignKeys'] = $table->getOption('foreignKeys', array()); - - if ($table->getAttribute(IPF_ORM::ATTR_EXPORT) & IPF_ORM::EXPORT_CONSTRAINTS) { - $constraints = array(); - - $emptyIntegrity = array('onUpdate' => null, - 'onDelete' => null); - - foreach ($table->getRelations() as $name => $relation) { - $fk = $relation->toArray(); - $fk['foreignTable'] = $relation->getTable()->getTableName(); - - if ($relation->getTable() === $table && in_array($relation->getLocal(), $primary)) { - if ($relation->hasConstraint()) { - throw new IPF_ORM_Exception("Badly constructed integrity constraints."); - } - continue; - } - - $integrity = array('onUpdate' => $fk['onUpdate'], - 'onDelete' => $fk['onDelete']); - - if ($relation instanceof IPF_ORM_Relation_LocalKey) { - $def = array('local' => $relation->getLocal(), - 'foreign' => $relation->getForeign(), - 'foreignTable' => $relation->getTable()->getTableName()); - - if (($key = array_search($def, $options['foreignKeys'])) === false) { - $options['foreignKeys'][] = $def; - $constraints[] = $integrity; - } else { - if ($integrity !== $emptyIntegrity) { - $constraints[$key] = $integrity; - } - } - } - } - - foreach ($constraints as $k => $def) { - $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def); - } - } - - $options['primary'] = $primary; - - return array('tableName' => $table->getOption('tableName'), - 'columns' => $columns, - 'options' => array_merge($table->getOptions(), $options)); - } -} - diff --git a/ipf/legacy_orm/orm/export/mysql.php b/ipf/legacy_orm/orm/export/mysql.php deleted file mode 100644 index 1dbe90a..0000000 --- a/ipf/legacy_orm/orm/export/mysql.php +++ /dev/null @@ -1,493 +0,0 @@ -getFieldDeclarationList($fields); - - // build indexes for all foreign key fields (needed in MySQL!!) - if (isset($options['foreignKeys'])) { - foreach ($options['foreignKeys'] as $fk) { - $local = $fk['local']; - $found = false; - if (isset($options['indexes'])) { - foreach ($options['indexes'] as $definition) { - if (is_string($definition['fields'])) { - // Check if index already exists on the column - $found = ($local == $definition['fields']); - } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) { - // Index already exists on the column - $found = true; - } - } - } - if (isset($options['primary']) && !empty($options['primary']) && - in_array($local, $options['primary'])) { - // field is part of the PK and therefore already indexed - $found = true; - } - - if ( ! $found) { - if (is_array($local)) { - foreach($local as $localidx) { - $options['indexes'][$localidx] = array('fields' => array($localidx => array())); - } - } else { - $options['indexes'][$local] = array('fields' => array($local => array())); - } - } - } - } - - // add all indexes - if (isset($options['indexes']) && ! empty($options['indexes'])) { - foreach($options['indexes'] as $index => $definition) { - $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition); - } - } - - // attach all primary keys - if (isset($options['primary']) && ! empty($options['primary'])) { - $keyColumns = array_values($options['primary']); - $keyColumns = array_map(array($this->conn, 'quoteIdentifier'), $keyColumns); - $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; - } - - $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name) . ' (' . $queryFields . ')'; - - $optionStrings = array(); - - if (isset($options['comment'])) { - $optionStrings['comment'] = 'COMMENT = ' . $this->conn->quote($options['comment'], 'text'); - } - if (isset($options['charset'])) { - $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset']; - } - if (isset($options['collate'])) { - $optionStrings['collate'] = 'COLLATE ' . $options['collate']; - } - - $type = false; - - // get the type of the table - if (isset($options['type'])) { - $type = $options['type']; - } else { - $type = $this->conn->getAttribute(IPF_ORM::ATTR_DEFAULT_TABLE_TYPE); - } - - if ($type) { - $optionStrings[] = 'ENGINE = ' . $type; - } - - if ( ! empty($optionStrings)) { - $query.= ' '.implode(' ', $optionStrings); - } - $sql[] = $query; - - if (isset($options['foreignKeys'])) { - - foreach ((array) $options['foreignKeys'] as $k => $definition) { - if (is_array($definition)) { - $sql[] = $this->createForeignKeySql($name, $definition); - } - } - } - return $sql; - } - - public function getDeclaration($name, array $field) - { - $declaration = $this->conn->quoteIdentifier($name) . ' '; - - if (!isset($field['type'])) - throw new IPF_ORM_Exception('Missing column type.'); - - switch ($field['type']) { - case 'char': - $length = ( ! empty($field['length'])) ? $field['length'] : false; - - $declaration .= $length ? 'CHAR('.$length.')' : 'CHAR(255)'; - break; - case 'varchar': - case 'array': - case 'object': - case 'string': - case 'gzip': - if (!isset($field['length'])) { - if (array_key_exists('default', $field)) { - $field['length'] = $this->conn->varchar_max_length; - } else { - $field['length'] = false; - } - } - - $length = ($field['length'] <= $this->conn->varchar_max_length) ? $field['length'] : false; - $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; - - $declaration .= $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') - : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); - break; - case 'clob': - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 255) { - return 'TINYTEXT'; - } elseif ($length <= 65532) { - return 'TEXT'; - } elseif ($length <= 16777215) { - return 'MEDIUMTEXT'; - } - } - $declaration .= 'LONGTEXT'; - break; - case 'blob': - if ( ! empty($field['length'])) { - $length = $field['length']; - if ($length <= 255) { - return 'TINYBLOB'; - } elseif ($length <= 65532) { - return 'BLOB'; - } elseif ($length <= 16777215) { - return 'MEDIUMBLOB'; - } - } - $declaration .= 'LONGBLOB'; - break; - case 'enum': - if ($this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { - $values = array(); - foreach ($field['values'] as $value) { - $values[] = $this->conn->quote($value, 'varchar'); - } - $declaration .= 'ENUM('.implode(', ', $values).')'; - break; - } - // fall back to integer - case 'integer': - case 'int': - $type = 'INT'; - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 1) { - $type = 'TINYINT'; - } elseif ($length == 2) { - $type = 'SMALLINT'; - } elseif ($length == 3) { - $type = 'MEDIUMINT'; - } elseif ($length == 4) { - $type = 'INT'; - } elseif ($length > 4) { - $type = 'BIGINT'; - } - } - $declaration .= $type; - if (isset($field['unsigned']) && $field['unsigned']) - $declaration .= ' UNSIGNED'; - break; - case 'boolean': - $declaration .= 'TINYINT(1)'; - break; - case 'date': - $declaration .= 'DATE'; - break; - case 'time': - $declaration .= 'TIME'; - break; - case 'datetime': - $declaration .= 'DATETIME'; - break; - case 'timestamp': - $declaration .= 'TIMESTAMP'; - break; - case 'float': - case 'double': - $declaration .= 'DOUBLE'; - break; - case 'decimal': - $scale = !empty($field['scale']) ? $field['scale'] : $this->conn->getAttribute(IPF_ORM::ATTR_DECIMAL_PLACES); - if (!empty($field['length'])) { - $length = $field['length']; - if (is_array($length)) { - list($length, $scale) = $length; - } - } else { - $length = 18; - } - $declaration .= 'DECIMAL('.$length.','.$scale.')'; - break; - case 'bit': - $declaration .= 'BIT'; - break; - default: - throw new IPF_ORM_Exception('Unknown field type \'' . $field['type'] . '\'.'); - } - - if (isset($field['charset']) && $field['charset']) - $declaration .= ' CHARACTER SET ' . $field['charset']; - - if (isset($field['collate']) && $field['collate']) - $declaration .= ' COLLATE ' . $field['collate']; - - if (isset($field['notnull']) && $field['notnull']) - $declaration .= ' NOT NULL'; - - if (!empty($field['autoincrement'])) { - $declaration .= ' AUTO_INCREMENT'; - } else { - $declaration .= $this->getDefaultFieldDeclaration($field); - - if (isset($field['unique']) && $field['unique']) - $declaration .= ' UNIQUE'; - } - - if (isset($field['comment']) && $field['comment']) - $declaration .= ' COMMENT ' . $this->conn->quote($field['comment'], 'varchar'); - - return $declaration; - } - - private function getDefaultFieldDeclaration($field) - { - $default = ''; - if (isset($field['default']) && (!isset($field['length']) || $field['length'] <= 255)) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) ? null : $this->valid_default_values[$field['type']]; - } - - if ($field['default'] === '' && ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EMPTY_TO_NULL)) - $field['default'] = null; - - if (is_null($field['default'])) { - $default = ' DEFAULT NULL'; - } else { - if ($field['type'] === 'boolean') { - $fieldType = 'boolean'; - $field['default'] = $this->conn->convertBooleans($field['default']); - } elseif ($field['type'] == 'enum' && $this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { - $fieldType = 'varchar'; - } else { - $fieldType = $field['type']; - } - $default = ' DEFAULT ' . $this->conn->quote($field['default'], $fieldType); - } - } - return $default; - } - - public function alterTableSql($name, array $changes, $check = false) - { - if ( ! $name) { - throw new IPF_ORM_Exception('no valid table name specified'); - } - foreach ($changes as $changeName => $change) { - switch ($changeName) { - case 'add': - case 'remove': - case 'change': - case 'rename': - case 'name': - break; - default: - throw new IPF_ORM_Exception('change type "' . $changeName . '" not yet supported'); - } - } - - if ($check) { - return true; - } - - $query = ''; - if ( ! empty($changes['name'])) { - $change_name = $this->conn->quoteIdentifier($changes['name']); - $query .= 'RENAME TO ' . $change_name; - } - - if ( ! empty($changes['add']) && is_array($changes['add'])) { - foreach ($changes['add'] as $fieldName => $field) { - if ($query) { - $query.= ', '; - } - $query.= 'ADD ' . $this->getDeclaration($fieldName, $field); - } - } - - if ( ! empty($changes['remove']) && is_array($changes['remove'])) { - foreach ($changes['remove'] as $fieldName => $field) { - if ($query) { - $query .= ', '; - } - $fieldName = $this->conn->quoteIdentifier($fieldName); - $query .= 'DROP ' . $fieldName; - } - } - - $rename = array(); - if ( ! empty($changes['rename']) && is_array($changes['rename'])) { - foreach ($changes['rename'] as $fieldName => $field) { - $rename[$field['name']] = $fieldName; - } - } - - if ( ! empty($changes['change']) && is_array($changes['change'])) { - foreach ($changes['change'] as $fieldName => $field) { - if ($query) { - $query.= ', '; - } - if (isset($rename[$fieldName])) { - $oldFieldName = $rename[$fieldName]; - unset($rename[$fieldName]); - } else { - $oldFieldName = $fieldName; - } - $oldFieldName = $this->conn->quoteIdentifier($oldFieldName); - $query .= 'CHANGE ' . $oldFieldName . ' ' - . $this->getDeclaration($fieldName, $field['definition']); - } - } - - if ( ! empty($rename) && is_array($rename)) { - foreach ($rename as $renameName => $renamedField) { - if ($query) { - $query.= ', '; - } - $field = $changes['rename'][$renamedField]; - $renamedField = $this->conn->quoteIdentifier($renamedField); - $query .= 'CHANGE ' . $renamedField . ' ' - . $this->getDeclaration($field['name'], $field['definition']); - } - } - - if ( ! $query) { - return false; - } - - $name = $this->conn->quoteIdentifier($name); - - return 'ALTER TABLE ' . $name . ' ' . $query; - } - - public function createIndexSql($table, $name, array $definition) - { - $name = $this->conn->quoteIdentifier($this->getIndexName($name)); - $type = ''; - if (isset($definition['type'])) { - switch (strtolower($definition['type'])) { - case 'fulltext': - case 'unique': - $type = strtoupper($definition['type']) . ' '; - break; - default: - throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); - } - } - $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; - $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; - - return $query; - } - - public function getIndexDeclaration($name, array $definition) - { - $name = $this->getIndexName($name); - $type = ''; - if (isset($definition['type'])) { - switch (strtolower($definition['type'])) { - case 'fulltext': - case 'unique': - $type = strtoupper($definition['type']) . ' '; - break; - default: - throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); - } - } - - if ( ! isset($definition['fields'])) { - throw new IPF_ORM_Exception('No index columns given.'); - } - if ( ! is_array($definition['fields'])) { - $definition['fields'] = array($definition['fields']); - } - - $query = $type . 'INDEX ' . $this->conn->quoteIdentifier($name); - - $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; - - return $query; - } - - public function getIndexFieldDeclarationList(array $fields) - { - $declFields = array(); - - foreach ($fields as $fieldName => $field) { - $fieldString = $this->conn->quoteIdentifier($fieldName); - - if (is_array($field)) { - if (isset($field['length'])) { - $fieldString .= '(' . $field['length'] . ')'; - } - - if (isset($field['sorting'])) { - $sort = strtoupper($field['sorting']); - switch ($sort) { - case 'ASC': - case 'DESC': - $fieldString .= ' ' . $sort; - break; - default: - throw new IPF_ORM_Exception('Unknown index sorting option given.'); - } - } - } else { - $fieldString = $this->conn->quoteIdentifier($field); - } - $declFields[] = $fieldString; - } - return implode(', ', $declFields); - } - - public function getAdvancedForeignKeyOptions(array $definition) - { - $query = ''; - if ( ! empty($definition['match'])) { - $query .= ' MATCH ' . $definition['match']; - } - if ( ! empty($definition['onUpdate'])) { - $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']); - } - if ( ! empty($definition['onDelete'])) { - $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']); - } - return $query; - } - - public function dropIndexSql($table, $name) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($this->getIndexName($name)); - return 'DROP INDEX ' . $name . ' ON ' . $table; - } - - public function dropTableSql($table) - { - $table = $this->conn->quoteIdentifier($table); - return 'DROP TABLE ' . $table; - } - - public function dropForeignKey($table, $name) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($name); - - return $this->conn->exec('ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $name); - } -} - diff --git a/ipf/legacy_orm/orm/expression.php b/ipf/legacy_orm/orm/expression.php deleted file mode 100644 index a866412..0000000 --- a/ipf/legacy_orm/orm/expression.php +++ /dev/null @@ -1,72 +0,0 @@ -_tokenizer = new IPF_ORM_Query_Tokenizer(); - $this->setExpression($expr); - if ($conn !== null) { - $this->_conn = $conn; - } - } - - public function getConnection() - { - if ( ! isset($this->_conn)) { - return IPF_ORM_Manager::connection(); - } - - return $this->_conn; - } - - public function setExpression($clause) - { - $this->_expression = $this->parseClause($clause); - } - - public function parseExpression($expr) - { - $pos = strpos($expr, '('); - $quoted = (substr($expr, 0, 1) === "'" && substr($expr, -1) === "'"); - if ($pos === false || $quoted) { - return $expr; - } - - // get the name of the function - $name = substr($expr, 0, $pos); - $argStr = substr($expr, ($pos + 1), -1); - - // parse args - foreach ($this->_tokenizer->bracketExplode($argStr, ',') as $arg) { - $args[] = $this->parseClause($arg); - } - - return call_user_func_array(array($this->getConnection()->expression, $name), $args); - } - - public function parseClause($clause) - { - $e = $this->_tokenizer->bracketExplode($clause, ' '); - - foreach ($e as $k => $expr) { - $e[$k] = $this->parseExpression($expr); - } - - return implode(' ', $e); - } - - public function getSql() - { - return $this->_expression; - } - - public function __toString() - { - return $this->getSql(); - } -} diff --git a/ipf/legacy_orm/orm/expression/driver.php b/ipf/legacy_orm/orm/expression/driver.php deleted file mode 100644 index 5c7baa2..0000000 --- a/ipf/legacy_orm/orm/expression/driver.php +++ /dev/null @@ -1,288 +0,0 @@ -getIdentifier($column); - return 'AVG(' . $column . ')'; - } - - public function count($column) - { - $column = $this->getIdentifier($column); - return 'COUNT(' . $column . ')'; - } - - public function max($column) - { - $column = $this->getIdentifier($column); - return 'MAX(' . $column . ')'; - } - - public function min($column) - { - $column = $this->getIdentifier($column); - return 'MIN(' . $column . ')'; - } - - public function sum($column) - { - $column = $this->getIdentifier($column); - return 'SUM(' . $column . ')'; - } - - public function md5($column) - { - $column = $this->getIdentifier($column); - return 'MD5(' . $column . ')'; - } - - public function length($column) - { - $column = $this->getIdentifier($column); - return 'LENGTH(' . $column . ')'; - } - - public function round($column, $decimals = 0) - { - $column = $this->getIdentifier($column); - - return 'ROUND(' . $column . ', ' . $decimals . ')'; - } - - public function mod($expression1, $expression2) - { - $expression1 = $this->getIdentifier($expression1); - $expression2 = $this->getIdentifier($expression2); - return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; - } - - public function trim($str) - { - return 'TRIM(' . $str . ')'; - } - - public function rtrim($str) - { - return 'RTRIM(' . $str . ')'; - } - - public function ltrim($str) - { - return 'LTRIM(' . $str . ')'; - } - - public function upper($str) - { - return 'UPPER(' . $str . ')'; - } - - public function lower($str) - { - return 'LOWER(' . $str . ')'; - } - - public function locate($str, $substr) - { - return 'LOCATE(' . $str . ', ' . $substr . ')'; - } - - public function now() - { - return 'NOW()'; - } - - public function coalesce($expression1, $expression2) - { - $expression1 = $this->getIdentifier($expression1); - $expression2 = $this->getIdentifier($expression2); - return 'COALESCE(' . $expression1 . ', ' . $expression2 . ')'; - } - - public function soundex($value) - { - throw new IPF_ORM_Exception('SQL soundex function not supported by this driver.'); - } - - public function substring($value, $from, $len = null) - { - $value = $this->getIdentifier($value); - if ($len === null) - return 'SUBSTRING(' . $value . ' FROM ' . $from . ')'; - else { - $len = $this->getIdentifier($len); - return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $len . ')'; - } - } - - public function concat() - { - $args = func_get_args(); - - return 'CONCAT(' . join(', ', (array) $args) . ')'; - } - - public function not($expression) - { - $expression = $this->getIdentifier($expression); - return 'NOT(' . $expression . ')'; - } - - private function basicMath($type, array $args) - { - $elements = $this->getIdentifiers($args); - if (count($elements) < 1) { - return ''; - } - if (count($elements) == 1) { - return $elements[0]; - } else { - return '(' . implode(' ' . $type . ' ', $elements) . ')'; - } - } - - public function add(array $args) - { - return $this->basicMath('+', $args); - } - - public function sub(array $args) - { - return $this->basicMath('-', $args ); - } - - public function mul(array $args) - { - return $this->basicMath('*', $args); - } - - public function div(array $args) - { - return $this->basicMath('/', $args); - } - - public function eq($value1, $value2) - { - $value1 = $this->getIdentifier($value1); - $value2 = $this->getIdentifier($value2); - return $value1 . ' = ' . $value2; - } - - public function neq($value1, $value2) - { - $value1 = $this->getIdentifier($value1); - $value2 = $this->getIdentifier($value2); - return $value1 . ' <> ' . $value2; - } - - public function gt($value1, $value2) - { - $value1 = $this->getIdentifier($value1); - $value2 = $this->getIdentifier($value2); - return $value1 . ' > ' . $value2; - } - - public function gte($value1, $value2) - { - $value1 = $this->getIdentifier($value1); - $value2 = $this->getIdentifier($value2); - return $value1 . ' >= ' . $value2; - } - - public function lt($value1, $value2) - { - $value1 = $this->getIdentifier($value1); - $value2 = $this->getIdentifier($value2); - return $value1 . ' < ' . $value2; - } - - public function lte($value1, $value2) - { - $value1 = $this->getIdentifier($value1); - $value2 = $this->getIdentifier($value2); - return $value1 . ' <= ' . $value2; - } - - public function in($column, $values) - { - if ( ! is_array($values)) { - $values = array($values); - } - $values = $this->getIdentifiers($values); - $column = $this->getIdentifier($column); - - if (count($values) == 0) { - throw new IPF_ORM_Exception('Values array for IN operator should not be empty.'); - } - return $column . ' IN (' . implode(', ', $values) . ')'; - } - - public function isNull($expression) - { - $expression = $this->getIdentifier($expression); - return $expression . ' IS NULL'; - } - - public function isNotNull($expression) - { - $expression = $this->getIdentifier($expression); - return $expression . ' IS NOT NULL'; - } - - public function between($expression, $value1, $value2) - { - $expression = $this->getIdentifier($expression); - $value1 = $this->getIdentifier($value1); - $value2 = $this->getIdentifier($value2); - return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2; - } - - public function guid() - { - throw new IPF_ORM_Exception('method not implemented'); - } - - public function acos($value) - { - return 'ACOS(' . $value . ')'; - } - - public function sin($value) - { - return 'SIN(' . $value . ')'; - } - - public function pi() - { - return 'PI()'; - } - - public function cos($value) - { - return 'COS(' . $value . ')'; - } - - public function __call($m, $a) - { - if ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EXPR) { - throw new IPF_ORM_Exception('Unknown expression ' . $m); - } - return $m . '(' . implode(', ', $a) . ')'; - } -} - diff --git a/ipf/legacy_orm/orm/expression/mysql.php b/ipf/legacy_orm/orm/expression/mysql.php deleted file mode 100644 index cc11fc6..0000000 --- a/ipf/legacy_orm/orm/expression/mysql.php +++ /dev/null @@ -1,43 +0,0 @@ -getIdentifier($column); - return 'YEAR(' . $column . ')'; - } - - public function month($column) - { - $column = $this->getIdentifier($column); - return 'MONTH(' . $column . ')'; - } - - public function monthname($column) - { - $column = $this->getIdentifier($column); - return 'MONTHNAME(' . $column . ')'; - } - - public function day($column) - { - $column = $this->getIdentifier($column); - return 'DAY(' . $column . ')'; - } -} diff --git a/ipf/legacy_orm/orm/hydrator.php b/ipf/legacy_orm/orm/hydrator.php deleted file mode 100644 index a4bb6bf..0000000 --- a/ipf/legacy_orm/orm/hydrator.php +++ /dev/null @@ -1,271 +0,0 @@ -_hydrationMode; - - $this->_tableAliases = $tableAliases; - - if ($hydrationMode == IPF_ORM::HYDRATE_NONE) { - return $stmt->fetchAll(PDO::FETCH_NUM); - } - - if ($hydrationMode == IPF_ORM::HYDRATE_ARRAY) { - $driver = new IPF_ORM_Hydrator_ArrayDriver(); - } else { - $driver = new IPF_ORM_Hydrator_RecordDriver(); - } - - // Used variables during hydration - reset($this->_queryComponents); - $rootAlias = key($this->_queryComponents); - $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getComponentName(); - // if only one component is involved we can make our lives easier - $isSimpleQuery = count($this->_queryComponents) <= 1; - // Holds the resulting hydrated data structure - $result = array(); - // Lookup map to quickly discover/lookup existing records in the result - $identifierMap = array(); - // Holds for each component the last previously seen element in the result set - $prev = array(); - // holds the values of the identifier/primary key fields of components, - // separated by a pipe '|' and grouped by component alias (r, u, i, ... whatever) - // the $idTemplate is a prepared template. $id is set to a fresh template when - // starting to process a row. - $id = array(); - $idTemplate = array(); - - $result = $driver->getElementCollection($rootComponentName); - - if ($stmt === false || $stmt === 0) { - return $result; - } - - // Initialize - foreach ($this->_queryComponents as $dqlAlias => $data) { - $componentName = $data['table']->getComponentName(); - $identifierMap[$dqlAlias] = array(); - $prev[$dqlAlias] = null; - $idTemplate[$dqlAlias] = ''; - } - - $event = new IPF_ORM_Event(null, IPF_ORM_Event::HYDRATE, null); - - // Process result set - $cache = array(); - while ($data = $stmt->fetch(IPF_ORM::FETCH_ASSOC)) { - $id = $idTemplate; // initialize the id-memory - $nonemptyComponents = array(); - $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); - - // - // hydrate the data of the root component from the current row - // - $table = $this->_queryComponents[$rootAlias]['table']; - $componentName = $table->getComponentName(); - // Ticket #1115 (getInvoker() should return the component that has addEventListener) - $event->setInvoker($table); - $event->set('data', $rowData[$rootAlias]); - $table->notifyRecordListeners('preHydrate', $event); - - $index = false; - - // Check for an existing element - if ($isSimpleQuery || ! isset($identifierMap[$rootAlias][$id[$rootAlias]])) { - $element = $driver->getElement($rowData[$rootAlias], $componentName); - $event->set('data', $element); - $table->notifyRecordListeners('postHydrate', $event); - - // do we need to index by a custom field? - if ($field = $this->_getCustomIndexField($rootAlias)) { - if (isset($result[$field])) { - throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping."); - } else if ( ! isset($element[$field])) { - throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key."); - } - $result[$element[$field]] = $element; - } else { - $result[] = $element; - } - - $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($result); - } else { - $index = $identifierMap[$rootAlias][$id[$rootAlias]]; - } - - $this->_setLastElement($prev, $result, $index, $rootAlias, false); - unset($rowData[$rootAlias]); - - // end hydrate data of the root component for the current row - - - // $prev[$rootAlias] now points to the last element in $result. - // now hydrate the rest of the data found in the current row, that belongs to other - // (related) components. - foreach ($rowData as $dqlAlias => $data) { - $index = false; - $map = $this->_queryComponents[$dqlAlias]; - $table = $map['table']; - $componentName = $table->getComponentName(); - $event->set('data', $data); - $table->notifyRecordListeners('preHydrate', $event); - - $parent = $map['parent']; - $relation = $map['relation']; - $relationAlias = $map['relation']->getAlias(); - - $path = $parent . '.' . $dqlAlias; - - if ( ! isset($prev[$parent])) { - unset($prev[$dqlAlias]); // Ticket #1228 - continue; - } - - // check the type of the relation - if ( ! $relation->isOneToOne() && $driver->initRelated($prev[$parent], $relationAlias)) { - $oneToOne = false; - // append element - if (isset($nonemptyComponents[$dqlAlias])) { - $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); - $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; - $indexIsValid = $index !== false ? isset($prev[$parent][$relationAlias][$index]) : false; - if ( ! $indexExists || ! $indexIsValid) { - $element = $driver->getElement($data, $componentName); - $event->set('data', $element); - $table->notifyRecordListeners('postHydrate', $event); - - if ($field = $this->_getCustomIndexField($dqlAlias)) { - if (isset($prev[$parent][$relationAlias][$element[$field]])) { - throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping."); - } else if ( ! isset($element[$field])) { - throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key."); - } - $prev[$parent][$relationAlias][$element[$field]] = $element; - } else { - $prev[$parent][$relationAlias][] = $element; - } - $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey($prev[$parent][$relationAlias]); - } - // register collection for later snapshots - $driver->registerCollection($prev[$parent][$relationAlias]); - } - } else { - // 1-1 relation - $oneToOne = true; - - if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($prev[$parent][$relationAlias])) { - $prev[$parent][$relationAlias] = $driver->getNullPointer(); - } else if ( ! isset($prev[$parent][$relationAlias])) { - $element = $driver->getElement($data, $componentName); - - // [FIX] Tickets #1205 and #1237 - $event->set('data', $element); - $table->notifyRecordListeners('postHydrate', $event); - - $prev[$parent][$relationAlias] = $element; - } - } - - $coll =& $prev[$parent][$relationAlias]; - $this->_setLastElement($prev, $coll, $index, $dqlAlias, $oneToOne); - } - } - - $stmt->closeCursor(); - $driver->flush(); - //$e = microtime(true); - //echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records
'; - - return $result; - } - - protected function _setLastElement(&$prev, &$coll, $index, $dqlAlias, $oneToOne) - { - if (IPF_ORM_Null::isNull($coll) || $coll === null) { - unset($prev[$dqlAlias]); // Ticket #1228 - return; - } - - if ($index !== false) { - // Link element at $index to previous element for the component - // identified by the DQL alias $alias - $prev[$dqlAlias] =& $coll[$index]; - return; - } - - if (is_array($coll) && $coll) { - if ($oneToOne) { - $prev[$dqlAlias] =& $coll; - } else { - end($coll); - $prev[$dqlAlias] =& $coll[key($coll)]; - } - } else if (count($coll) > 0) { - $prev[$dqlAlias] = $coll->getLast(); - } - } - - protected function _gatherRowData(&$data, &$cache, &$id, &$nonemptyComponents) - { - $rowData = array(); - - foreach ($data as $key => $value) { - // Parse each column name only once. Cache the results. - if ( ! isset($cache[$key])) { - // check ignored names. fastest solution for now. if we get more we'll start - // to introduce a list. - if ($key == 'IPF_ORM_ROWNUM') continue; - $e = explode('__', $key); - $last = strtolower(array_pop($e)); - $cache[$key]['dqlAlias'] = $this->_tableAliases[strtolower(implode('__', $e))]; - $table = $this->_queryComponents[$cache[$key]['dqlAlias']]['table']; - $fieldName = $table->getFieldName($last); - $cache[$key]['fieldName'] = $fieldName; - if ($table->isIdentifier($fieldName)) { - $cache[$key]['isIdentifier'] = true; - } else { - $cache[$key]['isIdentifier'] = false; - } - $type = $table->getTypeOfColumn($last); - if ($type == 'integer' || $type == 'string') { - $cache[$key]['isSimpleType'] = true; - } else { - $cache[$key]['type'] = $type; - $cache[$key]['isSimpleType'] = false; - } - } - - $map = $this->_queryComponents[$cache[$key]['dqlAlias']]; - $table = $map['table']; - $dqlAlias = $cache[$key]['dqlAlias']; - $fieldName = $cache[$key]['fieldName']; - if (isset($this->_queryComponents[$dqlAlias]['agg'][$fieldName])) { - $fieldName = $this->_queryComponents[$dqlAlias]['agg'][$fieldName]; - } - - if ($cache[$key]['isIdentifier']) { - $id[$dqlAlias] .= '|' . $value; - } - - if ($cache[$key]['isSimpleType']) { - $rowData[$dqlAlias][$fieldName] = $value; - } else { - $rowData[$dqlAlias][$fieldName] = $table->prepareValue( - $fieldName, $value, $cache[$key]['type']); - } - - if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { - $nonemptyComponents[$dqlAlias] = true; - } - } - - return $rowData; - } - - protected function _getCustomIndexField($alias) - { - return isset($this->_queryComponents[$alias]['map']) ? $this->_queryComponents[$alias]['map'] : null; - } -} diff --git a/ipf/legacy_orm/orm/hydrator/abstract.php b/ipf/legacy_orm/orm/hydrator/abstract.php deleted file mode 100644 index 497d2ca..0000000 --- a/ipf/legacy_orm/orm/hydrator/abstract.php +++ /dev/null @@ -1,32 +0,0 @@ -_hydrationMode = $hydrationMode; - } - - public function getHydrationMode() - { - return $this->_hydrationMode; - } - - public function setQueryComponents(array $queryComponents) - { - $this->_queryComponents = $queryComponents; - } - - public function getQueryComponents() - { - return $this->_queryComponents; - } - - abstract public function hydrateResultSet($stmt, $tableAliases); -} diff --git a/ipf/legacy_orm/orm/hydrator/arraydriver.php b/ipf/legacy_orm/orm/hydrator/arraydriver.php deleted file mode 100644 index 6018d62..0000000 --- a/ipf/legacy_orm/orm/hydrator/arraydriver.php +++ /dev/null @@ -1,40 +0,0 @@ -_collections[] = $coll; - - return $coll; - } - - public function getLastKey($coll) - { - $coll->end(); - - return $coll->key(); - } - - public function initRelated(IPF_ORM_Record $record, $name) - { - if ( ! isset($this->_initializedRelations[$record->getOid()][$name])) { - $relation = $record->getTable()->getRelation($name); - $coll = new IPF_ORM_Collection($relation->getTable()->getComponentName()); - $coll->setReference($record, $relation); - $record[$name] = $coll; - $this->_initializedRelations[$record->getOid()][$name] = true; - } - return true; - } - - public function registerCollection(IPF_ORM_Collection $coll) - { - $this->_collections[] = $coll; - } - - public function getNullPointer() - { - return IPF_ORM_Null::getInstance(); - } - - public function getElement(array $data, $component) - { - $component = $this->_getClassNameToReturn($data, $component); - if ( ! isset($this->_tables[$component])) { - $this->_tables[$component] = IPF_ORM::getTable($component); - $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false); - } - - $this->_tables[$component]->setData($data); - $record = $this->_tables[$component]->getRecord(); - - return $record; - } - - public function flush() - { - // take snapshots from all initialized collections - foreach ($this->_collections as $key => $coll) { - $coll->takeSnapshot(); - } - foreach ($this->_tables as $table) { - $table->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, true); - } - $this->_initializedRelations = null; - $this->_collections = null; - $this->_tables = null; - } - - protected function _getClassNameToReturn(array &$data, $component) - { - if ( ! isset($this->_tables[$component])) { - $this->_tables[$component] = IPF_ORM::getTable($component); - $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false); - } - - if ( ! ($subclasses = $this->_tables[$component]->getOption('subclasses'))) { - return $component; - } - - foreach ($subclasses as $subclass) { - $table = IPF_ORM::getTable($subclass); - $inheritanceMap = $table->getOption('inheritanceMap'); - list($key, $value) = each($inheritanceMap); - if ( ! isset($data[$key]) || $data[$key] != $value) { - continue; - } else { - return $table->getComponentName(); - } - } - return $component; - } -} diff --git a/ipf/legacy_orm/orm/import/builder.php b/ipf/legacy_orm/orm/import/builder.php deleted file mode 100644 index 9aaae70..0000000 --- a/ipf/legacy_orm/orm/import/builder.php +++ /dev/null @@ -1,388 +0,0 @@ -setTableName(\''. $definition['tableName'].'\');'; - - if (isset($definition['columns']) && is_array($definition['columns']) && !empty($definition['columns'])) - $ret[] = $this->buildColumns($definition['columns']); - - if (isset($definition['ordering']) && is_array($definition['ordering']) && !empty($definition['ordering'])) - $ret[] = ' $table->setOrdering('.self::varExportList($definition['ordering']).');'; - - if (isset($definition['indexes']) && is_array($definition['indexes']) && !empty($definition['indexes'])) - foreach ($definition['indexes'] as $indexName => $definitions) - $ret[] = " \$table->addIndex('" . $indexName . "', " . self::varExport($definitions) . ');'; - - if (isset($definition['attributes']) && is_array($definition['attributes']) && !empty($definition['attributes'])) - $ret[] = $this->buildAttributes($definition['attributes']); - - if (isset($definition['options']) && is_array($definition['options']) && !empty($definition['options'])) - $ret[] = $this->buildOptions($definition['options']); - - if (isset($definition['inheritance']['subclasses']) && !empty($definition['inheritance']['subclasses'])) { - $ret[] = " ".'$table->setSubClasses('. self::varExport($definition['inheritance']['subclasses']).');'; - } - - $ret[] = ' }'; - - return implode(PHP_EOL, $ret); - } - - private function buildSetUp(array $definition) - { - $ret = array(); - - if (isset($definition['relations']) && is_array($definition['relations']) && !empty($definition['relations'])) { - foreach ($definition['relations'] as $name => $relation) { - $class = isset($relation['class']) ? $relation['class'] : $name; - $alias = (isset($relation['alias']) && $relation['alias'] !== $relation['class']) ? $relation['alias'] : ''; - - if (!isset($relation['type'])) - $relation['type'] = IPF_ORM_Relation::ONE; - - if ($relation['type'] === IPF_ORM_Relation::ONE || - $relation['type'] === IPF_ORM_Relation::ONE_COMPOSITE) { - $r = " \$table->hasOne('$class', '$alias'"; - } else { - $r = " \$table->hasMany('$class', '$alias'"; - } - - $a = array(); - - if (isset($relation['refClass'])) { - $a[] = '\'refClass\' => ' . self::varExport($relation['refClass']); - } - - if (isset($relation['deferred']) && $relation['deferred']) { - $a[] = '\'default\' => ' . self::varExport($relation['deferred']); - } - - if (isset($relation['local']) && $relation['local']) { - $a[] = '\'local\' => ' . self::varExport($relation['local']); - } - - if (isset($relation['foreign']) && $relation['foreign']) { - $a[] = '\'foreign\' => ' . self::varExport($relation['foreign']); - } - - if (isset($relation['onDelete']) && $relation['onDelete']) { - $a[] = '\'onDelete\' => ' . self::varExport($relation['onDelete']); - } - - if (isset($relation['onUpdate']) && $relation['onUpdate']) { - $a[] = '\'onUpdate\' => ' . self::varExport($relation['onUpdate']); - } - - if (isset($relation['equal']) && $relation['equal']) { - $a[] = '\'equal\' => ' . self::varExport($relation['equal']); - } - - if (isset($relation['owningSide']) && $relation['owningSide']) { - $a[] = '\'owningSide\' => ' . self::varExport($relation['owningSide']); - } - - if (isset($relation['exclude']) && $relation['exclude']) { - $a[] = '\'exclude\' => ' . self::varExport($relation['exclude']); - } - - if (!empty($a)) - $r .= ', array(' . implode(', ', $a) . ')'; - - $ret[] = $r.');'; - } - } - - if (isset($definition['templates']) && is_array($definition['templates']) && !empty($definition['templates'])) { - $this->buildTemplates($definition['templates'], $ret); - } - - if (isset($definition['actAs']) && is_array($definition['actAs']) && !empty($definition['actAs'])) { - $this->buildActAs($definition['actAs'], $ret); - } - - if (isset($definition['listeners']) && is_array($definition['listeners']) && !empty($definition['listeners'])) { - foreach ($definition['listeners'] as $listener) { - $ret[] = " \$table->listeners['$listener'] = new $listener;"; - } - } - - // If the body of the function has contents and we are using inheritance - // then we need call the parent::setUp() before the body of the function - // Class table inheritance is the only one we shouldn't call parent::setUp() for - if (count($ret) && isset($definition['inheritance']['type']) && $definition['inheritance']['type'] != 'class_table') { - array_unshift($ret, ' parent::setUp($table);'); - } - - // If we have some code for the function then lets define it and return it - if (count($ret)) { - array_unshift($ret, - ' public static function setUp(IPF_ORM_Table $table)', - ' {' - ); - $ret[] = ' }'; - } - return $ret; - } - - private function buildColumns(array $columns) - { - $result = array(); - foreach ($columns as $name => $column) { - $columnName = isset($column['name']) ? $column['name']:$name; - $build = " ".'$table->setColumn(\'' . $columnName . '\', \'' . $column['type'] . '\''; - - if ($column['length']) { - if (is_numeric($column['length'])) - $build .= ', ' . $column['length']; - else - $build .= ', array(' . $column['length'] . ')'; - } else { - $build .= ', null'; - } - - $options = $column; - - // Remove name, alltypes, ntype. They are not needed in options array - unset($options['name']); - unset($options['alltypes']); - unset($options['ntype']); - - // Remove notnull => true if the column is primary - // Primary columns are implied to be notnull in IPF_ORM - if (isset($options['primary']) && $options['primary'] == true && (isset($options['notnull']) && $options['notnull'] == true)) { - unset($options['notnull']); - } - - // Remove default if the value is 0 and the column is a primary key - // IPF_ORM defaults to 0 if it is a primary key - if (isset($options['primary']) && $options['primary'] == true && (isset($options['default']) && $options['default'] == 0)) { - unset($options['default']); - } - - // These can be removed if they are empty. They all default to a false/0/null value anyways - $remove = array('fixed', 'primary', 'notnull', 'autoincrement', 'unsigned'); - foreach ($remove as $key) { - if (isset($options[$key]) && empty($options[$key])) { - unset($options[$key]); - } - } - - // Remove null and empty array values - foreach ($options as $key => $value) { - if (is_null($value) || (is_array($value) && empty($value))) { - unset($options[$key]); - } - } - - if (is_array($options) && !empty($options)) { - $build .= ', ' . self::varExport($options); - } - - $build .= ');'; - - $result[] = $build; - } - - return implode(PHP_EOL, $result); - } - - private function buildTemplates(array $templates, array &$build) - { - foreach ($templates as $name => $options) { - if (is_array($options) && !empty($options)) { - $build[] = " \$table->addTemplate('$name', " . self::varExport($options) . ");"; - } elseif (isset($templates[0])) { - $build[] = " \$table->addTemplate('$options');"; - } else { - $build[] = " \$table->addTemplate('$name');"; - } - } - } - - private function buildActAs($actAs, array &$build) - { - // rewrite special case of actAs: [Behavior] which gave [0] => Behavior - if (is_array($actAs) && isset($actAs[0]) && !is_array($actAs[0])) { - $actAs = array_flip($actAs); - } - - // rewrite special case of actAs: Behavior - if (!is_array($actAs)) - $actAs = array($actAs => ''); - - foreach ($actAs as $template => $options) { - // find class matching $name - if (class_exists('IPF_ORM_Template_'.$template, true)) - $classname = 'IPF_ORM_Template_'.$template; - else - $classname = $template; - - if (is_array($options)) - $options = self::varExport($options); - else - $options = ''; - - $build[] = " \$table->addTemplate(new $classname($options));"; - } - } - - private function buildAttributes(array $attributes) - { - $build = PHP_EOL; - foreach ($attributes as $key => $value) { - - if (is_bool($value)) { - $values = $value ? 'true':'false'; - } else { - if (!is_array($value)) - $value = array($value); - - $values = ''; - foreach ($value as $attr) { - $values .= "IPF_ORM::" . strtoupper($key) . "_" . strtoupper($attr) . ' ^ '; - } - - // Trim last ^ - $values = substr($values, 0, strlen($values) - 3); - } - - $build .= " \$table->setAttribute(IPF_ORM::ATTR_" . strtoupper($key) . ", " . $values . ");" . PHP_EOL; - } - - return $build; - } - - private function buildOptions(array $options) - { - $build = ''; - foreach ($options as $name => $value) { - $build .= " \$table->setOption('$name', " . self::varExport($value) . ");" . PHP_EOL; - } - return $build; - } - - public function buildRecord(array $definition, $targetPath) - { - if (!isset($definition['className'])) - throw new IPF_ORM_Exception('Missing class name.'); - - $this->writeBaseDefinition($definition, $targetPath); - $this->writeModelDefinition($definition, $targetPath); - } - - private function writeBaseDefinition(array $definition, $targetPath) - { - $code = array( - '_baseClassPrefix.$definition['className'].' extends IPF_ORM_Record'; - $code[] = '{'; - $code[] = $this->buildTableDefinition($definition); - $code[] = ''; - $code = array_merge($code, $this->buildSetUp($definition)); - $code[] = ''; - $code = array_merge($code, $this->buildShortcuts($definition)); - $code[] = '}'; - - $fileName = $this->_baseClassPrefix . $definition['className'] . '.php'; - $writePath = $targetPath . DIRECTORY_SEPARATOR . $this->_baseClassesDirectory; - IPF_Utils::makeDirectories($writePath); - $writePath .= DIRECTORY_SEPARATOR . $fileName; - - if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false) - throw new IPF_ORM_Exception("Couldn't write file " . $writePath); - } - - private function buildShortcuts(array $definition) - { - return array( - ' public static function table()', - ' {', - ' return IPF_ORM::getTable(\''.$definition['className'].'\');', - ' }', - '', - ' public static function query($alias=\'\')', - ' {', - ' return IPF_ORM::getTable(\''.$definition['className'].'\')->createQuery($alias);', - ' }', - ); - } - - private function writeModelDefinition(array $definition, $targetPath) - { - $className = $definition['className']; - $adminClassName = $className.'Admin'; - - $writePath = $targetPath . DIRECTORY_SEPARATOR . $className . '.php'; - if (file_exists($writePath)) - return; - - $code = array( - '_baseClassPrefix, $className), - '{', - '}', - '', - '/*', - 'class '.$adminClassName.' extends IPF_Admin_Model', - '{', - '}', - '', - '// Add following line to your admin.php to enable admin interface for this model', - "new $adminClassName('$className'),", - '*/', - '', - ); - - IPF_Utils::makeDirectories($targetPath); - if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false) - throw new IPF_ORM_Exception("Couldn't write file " . $writePath); - } -} - diff --git a/ipf/legacy_orm/orm/import/schema.php b/ipf/legacy_orm/orm/import/schema.php deleted file mode 100644 index 889e070..0000000 --- a/ipf/legacy_orm/orm/import/schema.php +++ /dev/null @@ -1,490 +0,0 @@ - array( - 'abstract', - 'connection', - 'className', - 'tableName', - 'connection', - 'relations', - 'columns', - 'indexes', - 'attributes', - 'templates', - 'actAs', - 'options', - 'inheritance', - 'detect_relations', - 'listeners', - 'ordering', - ), - 'column' => array( - 'name', - 'format', - 'fixed', - 'primary', - 'autoincrement', - 'type', - 'length', - 'size', - 'default', - 'scale', - 'values', - 'comment', - 'protected', - 'zerofill', - 'owner', - 'exclude', - 'collate', - 'charset', - ), - 'relation' => array( - 'key', - 'class', - 'alias', - 'type', - 'refClass', - 'local', - 'exclude', - 'foreign', - 'foreignClass', - 'foreignAlias', - 'foreignType', - 'foreignExclude', - 'autoComplete', - 'onDelete', - 'onUpdate', - 'equal', - 'owningSide', - ), - 'inheritance' => array( - 'type', - 'extends', - 'keyField', - 'keyValue', - ), - ); - - protected $_validators = array(); - - public function getValidators() - { - if (empty($this->_validators)) { - $this->_validators = IPF_ORM_Utils::getValidators(); - } - return $this->_validators; - } - - public function importSchema($filename, $extraAllwedReferences) - { - $filename = (array)$filename; - $array = array(); - foreach ($filename as $fn) { - $definitions = $this->parseSchema($fn); - foreach ($definitions as &$definition) - $definition['input_file'] = $fn; - $array = array_merge($array, $definitions); - } - - $array = $this->_buildRelationships($array, $extraAllwedReferences); - $array = $this->_processInheritance($array); - return $array; - } - - private function parseSchema($schema) - { - $defaults = array('abstract' => false, - 'className' => null, - 'tableName' => null, - 'connection' => null, - 'relations' => array(), - 'indexes' => array(), - 'attributes' => array(), - 'templates' => array(), - 'actAs' => array(), - 'options' => array(), - 'inheritance' => array(), - 'detect_relations' => false); - - $array = Spyc::YAMLLoad($schema); - - // Go through the schema and look for global values so we can assign them to each table/class - $globals = array(); - $globalKeys = array('connection', - 'attributes', - 'templates', - 'actAs', - 'options', - 'inheritance', - 'detect_relations'); - - // Loop over and build up all the global values and remove them from the array - foreach ($array as $key => $value) { - if (in_array($key, $globalKeys)) { - unset($array[$key]); - $globals[$key] = $value; - } - } - - // Apply the globals to each table if it does not have a custom value set already - foreach ($array as $className => $table) { - foreach ($globals as $key => $value) { - if (!isset($array[$className][$key])) { - $array[$className][$key] = $value; - } - } - } - - $build = array(); - - foreach ($array as $className => $table) { - $this->_validateSchemaElement('root', array_keys($table), $className); - - $columns = array(); - - $className = isset($table['className']) ? (string) $table['className']:(string) $className; - - if (isset($table['inheritance']['keyField']) || isset($table['inheritance']['keyValue'])) { - $table['inheritance']['type'] = 'column_aggregation'; - } - - if (isset($table['tableName']) && $table['tableName']) { - $tableName = $table['tableName']; - } else { - if (isset($table['inheritance']['type']) && ($table['inheritance']['type'] == 'column_aggregation')) { - $tableName = null; - } else { - $tableName = IPF_ORM_Inflector::tableize($className); - } - } - - $connection = isset($table['connection']) ? $table['connection']:'current'; - - $columns = isset($table['columns']) ? $table['columns']:array(); - - if ( ! empty($columns)) { - foreach ($columns as $columnName => $field) { - - // Support short syntax: my_column: integer(4) - if ( ! is_array($field)) { - $original = $field; - $field = array(); - $field['type'] = $original; - } - - $colDesc = array(); - if (isset($field['name'])) { - $colDesc['name'] = $field['name']; - } else { - $colDesc['name'] = $columnName; - } - - $this->_validateSchemaElement('column', array_keys($field), $className . '->columns->' . $colDesc['name']); - - // Support short type(length) syntax: my_column: { type: integer(4) } - $e = explode('(', $field['type']); - if (isset($e[0]) && isset($e[1])) { - $colDesc['type'] = $e[0]; - $colDesc['length'] = substr($e[1], 0, strlen($e[1]) - 1); - } else { - $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null; - $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null; - $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length']; - } - - $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null; - $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null; - $colDesc['default'] = isset($field['default']) ? $field['default']:null; - $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null; - $colDesc['values'] = isset($field['values']) ? (array) $field['values']:null; - - // Include all the specified and valid validators in the colDesc - $validators = $this->getValidators(); - - foreach ($validators as $validator) { - if (isset($field[$validator])) { - $colDesc[$validator] = $field[$validator]; - } - } - - $columns[(string) $columnName] = $colDesc; - } - } - - // Apply the default values - foreach ($defaults as $key => $defaultValue) { - if (isset($table[$key]) && ! isset($build[$className][$key])) { - $build[$className][$key] = $table[$key]; - } else { - $build[$className][$key] = isset($build[$className][$key]) ? $build[$className][$key]:$defaultValue; - } - } - - $build[$className]['className'] = $className; - $build[$className]['tableName'] = $tableName; - $build[$className]['columns'] = $columns; - - // Make sure that anything else that is specified in the schema makes it to the final array - $build[$className] = IPF_ORM_Utils::arrayDeepMerge($table, $build[$className]); - - // We need to keep track of the className for the connection - $build[$className]['connectionClassName'] = $build[$className]['className']; - } - - return $build; - } - - protected function _processInheritance($array) - { - // Apply default inheritance configuration - foreach ($array as $className => $definition) { - if ( ! empty($array[$className]['inheritance'])) { - $this->_validateSchemaElement('inheritance', array_keys($definition['inheritance']), $className . '->inheritance'); - - // Default inheritance to concrete inheritance - if ( ! isset($array[$className]['inheritance']['type'])) { - $array[$className]['inheritance']['type'] = 'concrete'; - } - - // Some magic for setting up the keyField and keyValue column aggregation options - // Adds keyField to the parent class automatically - if ($array[$className]['inheritance']['type'] == 'column_aggregation') { - // Set the keyField to 'type' by default - if ( ! isset($array[$className]['inheritance']['keyField'])) { - $array[$className]['inheritance']['keyField'] = 'type'; - } - - // Set the keyValue to the name of the child class if it does not exist - if ( ! isset($array[$className]['inheritance']['keyValue'])) { - $array[$className]['inheritance']['keyValue'] = $className; - } - - // Add the keyType column to the parent if a definition does not already exist - if ( ! isset($array[$array[$className]['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']])) { - $array[$definition['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']] = array('name' => $array[$className]['inheritance']['keyField'], 'type' => 'string', 'length' => 255); - } - } - } - } - - // Array of the array keys to move to the parent, and the value to default the child definition to - // after moving it. Will also populate the subclasses array for the inheritance parent - $moves = array('columns' => array()); - - foreach ($array as $className => $definition) { - if (!isset($definition['className'])) - continue; - $parent = $this->_findBaseSuperClass($array, $definition['className']); - // Move any definitions on the schema to the parent - if (isset($definition['inheritance']['extends']) && isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) { - foreach ($moves as $move => $resetValue) { - $array[$parent][$move] = IPF_ORM_Utils::arrayDeepMerge($array[$parent][$move], $definition[$move]); - $array[$definition['className']][$move] = $resetValue; - } - - // Populate the parents subclasses - if ($definition['inheritance']['type'] == 'column_aggregation') { - $array[$parent]['inheritance']['subclasses'][$definition['className']] = array($definition['inheritance']['keyField'] => $definition['inheritance']['keyValue']); - } - } - } - - return $array; - } - - protected function _findBaseSuperClass($array, $class) - { - if (isset($array[$class]['inheritance']['extends'])) { - return $this->_findBaseSuperClass($array, $array[$class]['inheritance']['extends']); - } else { - return $class; - } - } - - protected function _buildRelationships($array, $extraAllwedReferences) - { - // Handle auto detecting relations by the names of columns - // User.contact_id will automatically create User hasOne Contact local => contact_id, foreign => id - foreach ($array as $className => $properties) { - if (isset($properties['columns']) && ! empty($properties['columns']) && isset($properties['detect_relations']) && $properties['detect_relations']) { - foreach ($properties['columns'] as $column) { - // Check if the column we are inflecting has a _id on the end of it before trying to inflect it and find - // the class name for the column - if (strpos($column['name'], '_id')) { - $columnClassName = IPF_ORM_Inflector::classify(str_replace('_id', '', $column['name'])); - if (isset($array[$columnClassName]) && !isset($array[$className]['relations'][$columnClassName])) { - $array[$className]['relations'][$columnClassName] = array(); - - // Set the detected foreign key type and length to the same as the primary key - // of the related table - $type = isset($array[$columnClassName]['columns']['id']['type']) ? $array[$columnClassName]['columns']['id']['type']:'integer'; - $length = isset($array[$columnClassName]['columns']['id']['length']) ? $array[$columnClassName]['columns']['id']['length']:8; - $array[$className]['columns'][$column['name']]['type'] = $type; - $array[$className]['columns'][$column['name']]['length'] = $length; - } - } - } - } - } - - foreach ($array as $name => $properties) { - if (!isset($properties['relations'])) { - continue; - } - - $className = $properties['className']; - $relations = $properties['relations']; - - foreach ($relations as $alias => $relation) { - $class = isset($relation['class']) ? $relation['class'] : $alias; - if (!isset($array[$class]) && !in_array($class, $extraAllwedReferences)) { - print "Warning: Ignoring relation to unknown model '$class' in model '$className'. \n"; - continue; - } - $relation['class'] = $class; - $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias; - - // Attempt to guess the local and foreign - if (isset($relation['refClass'])) { - $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($name) . '_id'; - $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:IPF_ORM_Inflector::tableize($class) . '_id'; - } else { - $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($relation['class']) . '_id'; - $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id'; - } - - if (isset($relation['refClass'])) { - $relation['type'] = 'many'; - } - - if (isset($relation['type']) && $relation['type']) { - $relation['type'] = $relation['type'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY; - } else { - $relation['type'] = IPF_ORM_Relation::ONE; - } - - if (isset($relation['foreignType']) && $relation['foreignType']) { - $relation['foreignType'] = $relation['foreignType'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY; - } - - $relation['key'] = $this->_buildUniqueRelationKey($relation); - - $this->_validateSchemaElement('relation', array_keys($relation), $className . '->relation->' . $relation['alias']); - - $this->_relations[$className][$alias] = $relation; - } - } - - // Now we auto-complete opposite ends of relationships - $this->_autoCompleteOppositeRelations($extraAllwedReferences); - - // Make sure we do not have any duplicate relations - $this->_fixDuplicateRelations(); - - //$array['relations']; - // Set the full array of relationships for each class to the final array - foreach ($this->_relations as $className => $relations) { - if (!in_array($className, $extraAllwedReferences)) - $array[$className]['relations'] = $relations; - } - - return $array; - } - - protected function _autoCompleteOppositeRelations($extraAllwedReferences) - { - foreach ($this->_relations as $className => $relations) { - if (in_array($className, $extraAllwedReferences)) - continue; - - foreach ($relations as $alias => $relation) { - if ((isset($relation['equal']) && $relation['equal']) || (isset($relation['autoComplete']) && $relation['autoComplete'] === false)) { - continue; - } - - $newRelation = array(); - $newRelation['foreign'] = $relation['local']; - $newRelation['local'] = $relation['foreign']; - $newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass'] : $className; - $newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias'] : $className; - $newRelation['exclude'] = isset($relation['foreignExclude']) ? $relation['foreignExclude'] : false; - - // this is so that we know that this relation was autogenerated and - // that we do not need to include it if it is explicitly declared in the schema by the users. - $newRelation['autogenerated'] = true; - - if (isset($relation['refClass'])) { - $newRelation['refClass'] = $relation['refClass']; - $newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type']; - } else { - if(isset($relation['foreignType'])) { - $newRelation['type'] = $relation['foreignType']; - } else { - $newRelation['type'] = $relation['type'] === IPF_ORM_Relation::ONE ? IPF_ORM_Relation::MANY:IPF_ORM_Relation::ONE; - } - } - - // Make sure it doesn't already exist - if ( ! isset($this->_relations[$relation['class']][$newRelation['alias']])) { - $newRelation['key'] = $this->_buildUniqueRelationKey($newRelation); - $this->_relations[$relation['class']][$newRelation['alias']] = $newRelation; - } - } - } - } - - protected function _fixDuplicateRelations() - { - foreach($this->_relations as $className => $relations) { - // This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations - $existingRelations = array(); - $uniqueRelations = array(); - foreach ($relations as $relation) { - if ( ! in_array($relation['key'], $existingRelations)) { - $existingRelations[] = $relation['key']; - $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation)); - } else { - // check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it - if ( ! isset($relation['autogenerated']) || $relation['autogenerated'] != true) { - $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation)); - } - } - } - - $this->_relations[$className] = $uniqueRelations; - } - } - - protected function _buildUniqueRelationKey($relation) - { - return md5($relation['local'].$relation['foreign'].$relation['class'].(isset($relation['refClass']) ? $relation['refClass']:null)); - } - - protected function _validateSchemaElement($name, $element, $path) - { - $element = (array) $element; - - $validation = $this->_validation[$name]; - - // Validators are a part of the column validation - // This should be fixed, made cleaner - if ($name == 'column') { - $validators = $this->getValidators(); - $validation = array_merge($validation, $validators); - } - - $validation = array_flip($validation); - foreach ($element as $key => $value) { - if ( ! isset($validation[$value])) { - throw new IPF_ORM_Exception(sprintf('Invalid schema element named "' . $value . - '" at path "' . $path . '"')); - } - } - } -} - diff --git a/ipf/legacy_orm/orm/inflector.php b/ipf/legacy_orm/orm/inflector.php deleted file mode 100644 index 4946ac5..0000000 --- a/ipf/legacy_orm/orm/inflector.php +++ /dev/null @@ -1,341 +0,0 @@ - '\1zes', - '/^(ox)$/i' => '\1en', - '/([m|l])ouse$/i' => '\1ice', - '/(matr|vert|ind)ix|ex$/i' => '\1ices', - '/(x|ch|ss|sh)$/i' => '\1es', - '/([^aeiouy]|qu)ies$/i' => '\1y', - '/([^aeiouy]|qu)y$/i' => '\1ies', - '/(hive)$/i' => '\1s', - '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', - '/sis$/i' => 'ses', - '/([ti])um$/i' => '\1a', - '/(buffal|tomat)o$/i' => '\1oes', - '/(bu)s$/i' => '\1ses', - '/(alias|status)/i' => '\1es', - '/(octop|vir)us$/i' => '\1i', - '/(ax|test)is$/i' => '\1es', - '/s$/i' => 's', - '/$/' => 's'); - - $uncountable = array('equipment', - 'information', - 'rice', - 'money', - 'species', - 'series', - 'fish', - 'sheep'); - - $irregular = array('person' => 'people', - 'man' => 'men', - 'child' => 'children', - 'sex' => 'sexes', - 'move' => 'moves'); - - $lowercasedWord = strtolower($word); - - foreach ($uncountable as $_uncountable) { - if (substr($lowercasedWord, (-1 * strlen($_uncountable))) == $_uncountable) { - return $word; - } - } - - foreach ($irregular as $_plural=> $_singular) { - if (preg_match('/('.$_plural.')$/i', $word, $arr)) { - return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1) . substr($_singular,1), $word); - } - } - - foreach ($plural as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - - return false; - } - - public static function singularize($word) - { - $singular = array('/(quiz)zes$/i' => '\\1', - '/(matr)ices$/i' => '\\1ix', - '/(vert|ind)ices$/i' => '\\1ex', - '/^(ox)en/i' => '\\1', - '/(alias|status)es$/i' => '\\1', - '/([octop|vir])i$/i' => '\\1us', - '/(cris|ax|test)es$/i' => '\\1is', - '/(shoe)s$/i' => '\\1', - '/(o)es$/i' => '\\1', - '/(bus)es$/i' => '\\1', - '/([m|l])ice$/i' => '\\1ouse', - '/(x|ch|ss|sh)es$/i' => '\\1', - '/(m)ovies$/i' => '\\1ovie', - '/(s)eries$/i' => '\\1eries', - '/([^aeiouy]|qu)ies$/i' => '\\1y', - '/([lr])ves$/i' => '\\1f', - '/(tive)s$/i' => '\\1', - '/(hive)s$/i' => '\\1', - '/([^f])ves$/i' => '\\1fe', - '/(^analy)ses$/i' => '\\1sis', - '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\\1\\2sis', - '/([ti])a$/i' => '\\1um', - '/(n)ews$/i' => '\\1ews', - '/^(.{2,2})$/i' => '\\1', - '/s$/i' => ''); - - $uncountable = array('equipment', - 'information', - 'rice', - 'money', - 'species', - 'series', - 'fish', - 'sheep', - 'sms', - 'status', - 'access'); - - $irregular = array('person' => 'people', - 'man' => 'men', - 'child' => 'children', - 'sex' => 'sexes', - 'move' => 'moves'); - - $lowercasedWord = strtolower($word); - foreach ($uncountable as $_uncountable) { - if (substr($lowercasedWord, ( -1 * strlen($_uncountable))) == $_uncountable) { - return $word; - } - } - - foreach ($irregular as $_singular => $_plural) { - if (preg_match('/('.$_plural.')$/i', $word, $arr)) { - return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1).substr($_singular,1), $word); - } - } - - foreach ($singular as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - - return $word; - } - - public static function tableize($word) - { - // Would prefer this but it breaks unit tests. Forces the table underscore pattern - // return self::pluralize(self::underscore($name)); - return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); - } - - public static function classify($word) - { - return preg_replace_callback('~(_?)(_)([\w])~', array("IPF_ORM_Inflector", "classifyCallback"), ucfirst(strtolower($word))); - } - - public static function classifyCallback($matches) - { - return $matches[1] . strtoupper($matches[3]); - } - - public static function seemsUtf8($string) - { - for ($i = 0; $i < strlen($string); $i++) { - if (ord($string[$i]) < 0x80) continue; # 0bbbbbbb - elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb - elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb - elseif ((ord($string[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb - elseif ((ord($string[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb - elseif ((ord($string[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b - else return false; # Does not match any model - for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ? - if ((++$i == strlen($string)) || ((ord($string[$i]) & 0xC0) != 0x80)) - return false; - } - } - return true; - } - - public static function unaccent($string) - { - if (!preg_match('/[\x80-\xff]/', $string)) { - return $string; - } - - if (self::seemsUtf8($string)) { - $chars = array( - // Decompositions for Latin-1 Supplement - chr(195).chr(128) => 'A', chr(195).chr(129) => 'A', - chr(195).chr(130) => 'A', chr(195).chr(131) => 'A', - chr(195).chr(132) => 'A', chr(195).chr(133) => 'A', - chr(195).chr(135) => 'C', chr(195).chr(136) => 'E', - chr(195).chr(137) => 'E', chr(195).chr(138) => 'E', - chr(195).chr(139) => 'E', chr(195).chr(140) => 'I', - chr(195).chr(141) => 'I', chr(195).chr(142) => 'I', - chr(195).chr(143) => 'I', chr(195).chr(145) => 'N', - chr(195).chr(146) => 'O', chr(195).chr(147) => 'O', - chr(195).chr(148) => 'O', chr(195).chr(149) => 'O', - chr(195).chr(150) => 'O', chr(195).chr(153) => 'U', - chr(195).chr(154) => 'U', chr(195).chr(155) => 'U', - chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y', - chr(195).chr(159) => 's', chr(195).chr(160) => 'a', - chr(195).chr(161) => 'a', chr(195).chr(162) => 'a', - chr(195).chr(163) => 'a', chr(195).chr(164) => 'a', - chr(195).chr(165) => 'a', chr(195).chr(167) => 'c', - chr(195).chr(168) => 'e', chr(195).chr(169) => 'e', - chr(195).chr(170) => 'e', chr(195).chr(171) => 'e', - chr(195).chr(172) => 'i', chr(195).chr(173) => 'i', - chr(195).chr(174) => 'i', chr(195).chr(175) => 'i', - chr(195).chr(177) => 'n', chr(195).chr(178) => 'o', - chr(195).chr(179) => 'o', chr(195).chr(180) => 'o', - chr(195).chr(181) => 'o', chr(195).chr(182) => 'o', - chr(195).chr(182) => 'o', chr(195).chr(185) => 'u', - chr(195).chr(186) => 'u', chr(195).chr(187) => 'u', - chr(195).chr(188) => 'u', chr(195).chr(189) => 'y', - chr(195).chr(191) => 'y', - // Decompositions for Latin Extended-A - chr(196).chr(128) => 'A', chr(196).chr(129) => 'a', - chr(196).chr(130) => 'A', chr(196).chr(131) => 'a', - chr(196).chr(132) => 'A', chr(196).chr(133) => 'a', - chr(196).chr(134) => 'C', chr(196).chr(135) => 'c', - chr(196).chr(136) => 'C', chr(196).chr(137) => 'c', - chr(196).chr(138) => 'C', chr(196).chr(139) => 'c', - chr(196).chr(140) => 'C', chr(196).chr(141) => 'c', - chr(196).chr(142) => 'D', chr(196).chr(143) => 'd', - chr(196).chr(144) => 'D', chr(196).chr(145) => 'd', - chr(196).chr(146) => 'E', chr(196).chr(147) => 'e', - chr(196).chr(148) => 'E', chr(196).chr(149) => 'e', - chr(196).chr(150) => 'E', chr(196).chr(151) => 'e', - chr(196).chr(152) => 'E', chr(196).chr(153) => 'e', - chr(196).chr(154) => 'E', chr(196).chr(155) => 'e', - chr(196).chr(156) => 'G', chr(196).chr(157) => 'g', - chr(196).chr(158) => 'G', chr(196).chr(159) => 'g', - chr(196).chr(160) => 'G', chr(196).chr(161) => 'g', - chr(196).chr(162) => 'G', chr(196).chr(163) => 'g', - chr(196).chr(164) => 'H', chr(196).chr(165) => 'h', - chr(196).chr(166) => 'H', chr(196).chr(167) => 'h', - chr(196).chr(168) => 'I', chr(196).chr(169) => 'i', - chr(196).chr(170) => 'I', chr(196).chr(171) => 'i', - chr(196).chr(172) => 'I', chr(196).chr(173) => 'i', - chr(196).chr(174) => 'I', chr(196).chr(175) => 'i', - chr(196).chr(176) => 'I', chr(196).chr(177) => 'i', - chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij', - chr(196).chr(180) => 'J', chr(196).chr(181) => 'j', - chr(196).chr(182) => 'K', chr(196).chr(183) => 'k', - chr(196).chr(184) => 'k', chr(196).chr(185) => 'L', - chr(196).chr(186) => 'l', chr(196).chr(187) => 'L', - chr(196).chr(188) => 'l', chr(196).chr(189) => 'L', - chr(196).chr(190) => 'l', chr(196).chr(191) => 'L', - chr(197).chr(128) => 'l', chr(197).chr(129) => 'L', - chr(197).chr(130) => 'l', chr(197).chr(131) => 'N', - chr(197).chr(132) => 'n', chr(197).chr(133) => 'N', - chr(197).chr(134) => 'n', chr(197).chr(135) => 'N', - chr(197).chr(136) => 'n', chr(197).chr(137) => 'N', - chr(197).chr(138) => 'n', chr(197).chr(139) => 'N', - chr(197).chr(140) => 'O', chr(197).chr(141) => 'o', - chr(197).chr(142) => 'O', chr(197).chr(143) => 'o', - chr(197).chr(144) => 'O', chr(197).chr(145) => 'o', - chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe', - chr(197).chr(148) => 'R', chr(197).chr(149) => 'r', - chr(197).chr(150) => 'R', chr(197).chr(151) => 'r', - chr(197).chr(152) => 'R', chr(197).chr(153) => 'r', - chr(197).chr(154) => 'S', chr(197).chr(155) => 's', - chr(197).chr(156) => 'S', chr(197).chr(157) => 's', - chr(197).chr(158) => 'S', chr(197).chr(159) => 's', - chr(197).chr(160) => 'S', chr(197).chr(161) => 's', - chr(197).chr(162) => 'T', chr(197).chr(163) => 't', - chr(197).chr(164) => 'T', chr(197).chr(165) => 't', - chr(197).chr(166) => 'T', chr(197).chr(167) => 't', - chr(197).chr(168) => 'U', chr(197).chr(169) => 'u', - chr(197).chr(170) => 'U', chr(197).chr(171) => 'u', - chr(197).chr(172) => 'U', chr(197).chr(173) => 'u', - chr(197).chr(174) => 'U', chr(197).chr(175) => 'u', - chr(197).chr(176) => 'U', chr(197).chr(177) => 'u', - chr(197).chr(178) => 'U', chr(197).chr(179) => 'u', - chr(197).chr(180) => 'W', chr(197).chr(181) => 'w', - chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y', - chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z', - chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z', - chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z', - chr(197).chr(190) => 'z', chr(197).chr(191) => 's', - // Euro Sign - chr(226).chr(130).chr(172) => 'E', - // GBP (Pound) Sign - chr(194).chr(163) => ''); - - $string = strtr($string, $chars); - } else { - // Assume ISO-8859-1 if not UTF-8 - $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158) - .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194) - .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202) - .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210) - .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218) - .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227) - .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235) - .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243) - .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251) - .chr(252).chr(253).chr(255); - - $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy"; - - $string = strtr($string, $chars['in'], $chars['out']); - $doubleChars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254)); - $doubleChars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'); - $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); - } - - return $string; - } - - public static function translit($st) - { - $st = strtr($st, array( - 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', - 'е' => 'e', 'ё' => 'e', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', - 'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', - 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', - 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'ts', 'ч' => 'ch', - 'ш' => 'sh', 'щ' => 'shch', 'ъ' => '\'', 'ы' => 'i', 'ь' => '', - 'э' => 'e', 'ю' => 'yu', 'я' => 'ya', 'ї' => 'i', 'є' => 'ie', - - 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', - 'Е' => 'E', 'Ё' => 'E', 'Ж' => 'Zh', 'З' => 'Z', 'И' => 'I', - 'Й' => 'Y', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', - 'О' => 'O', 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', - 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'Ts', 'Ч' => 'Ch', - 'Ш' => 'Sh', 'Щ' => 'Shch', 'Ъ' => '\'', 'Ы' => 'I', 'Ь' => '', - 'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya', 'Ї' => 'Yi', 'Є' => 'Ye', - )); - return $st; - } - - public static function urlize($text) - { - // Remove all non url friendly characters with the unaccent function - $text = self::unaccent($text); - - // Transliterate cyrillic - $text = self::translit($text); - - // Remove all none word characters - $text = preg_replace('/\W/', ' ', $text); - - // More stripping. Replace spaces with dashes - $text = strtolower(preg_replace('/[^A-Z^a-z^0-9^\/]+/', '-', - preg_replace('/([a-z\d])([A-Z])/', '\1_\2', - preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', - preg_replace('/::/', '/', $text))))); - - return trim($text, '-'); - } -} - diff --git a/ipf/legacy_orm/orm/manager.php b/ipf/legacy_orm/orm/manager.php deleted file mode 100644 index 570de33..0000000 --- a/ipf/legacy_orm/orm/manager.php +++ /dev/null @@ -1,63 +0,0 @@ -attributes = array( - IPF_ORM::ATTR_LOAD_REFERENCES => true, - IPF_ORM::ATTR_PORTABILITY => IPF_ORM::PORTABILITY_ALL, - IPF_ORM::ATTR_EXPORT => IPF_ORM::EXPORT_ALL, - IPF_ORM::ATTR_DECIMAL_PLACES => 2, - IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE => 'ipf', - ); - } - - public static function getInstance() - { - static $instance; - if ( ! isset($instance)) { - $instance = new self(); - } - return $instance; - } - - public function getQueryRegistry() - { - if ( ! isset($this->_queryRegistry)) { - $this->_queryRegistry = new IPF_ORM_Query_Registry; - } - return $this->_queryRegistry; - } - - public function setQueryRegistry(IPF_ORM_Query_Registry $registry) - { - $this->_queryRegistry = $registry; - - return $this; - } - - public static function connection() - { - return IPF_ORM_Manager::getInstance()->getCurrentConnection(); - } - - public function getCurrentConnection() - { - if (!$this->_connection) { - $pdo = \PFF\Container::databaseConnection(); - $this->_connection = new IPF_ORM_Connection_Mysql($this, $pdo); - } - return $this->_connection; - } - - public function __toString() - { - return "
\nIPF_ORM_Manager\n
"; - } -} - diff --git a/ipf/legacy_orm/orm/null.php b/ipf/legacy_orm/orm/null.php deleted file mode 100644 index 6347618..0000000 --- a/ipf/legacy_orm/orm/null.php +++ /dev/null @@ -1,36 +0,0 @@ -_setExecuted(false); - - $this->_setQuery($query); - $this->_setPage($page); - - $this->setMaxPerPage($maxPerPage); - } - - protected function _initialize($params = array()) - { - // retrieve the number of items found - $count = $this->getCountQuery()->count($this->getCountQueryParams($params)); - - $this->_setNumResults($count); - $this->_setExecuted(true); // _adjustOffset relies of _executed equals true = getNumResults() - - $this->_adjustOffset(); - } - - protected function _adjustOffset() - { - // Define new total of pages - $this->_setLastPage( - max(1, ceil($this->getNumResults() / $this->getMaxPerPage())) - ); - $offset = ($this->getPage() - 1) * $this->getMaxPerPage(); - - // Assign new offset and limit to IPF_ORM_Query object - $p = $this->getQuery(); - $p->offset($offset); - $p->limit($this->getMaxPerPage()); - } - - public function getExecuted() - { - return $this->_executed; - } - - protected function _setExecuted($executed) - { - $this->_executed = $executed; - } - - public function getRange($rangeStyle, $options = array()) - { - $class = 'IPF_ORM_Pager_Range_' . ucfirst($rangeStyle); - return new $class($options, $this); - } - - public function getNumResults() - { - if ($this->getExecuted()) { - return $this->_numResults; - } - throw new IPF_ORM_Exception( - 'Cannot retrieve the number of results of a not yet executed Pager query' - ); - } - - protected function _setNumResults($nb) - { - $this->_numResults = $nb; - } - - public function getFirstPage() - { - return 1; - } - - public function getLastPage() - { - if ($this->getExecuted()) { - return $this->_lastPage; - } - - throw new IPF_ORM_Exception( - 'Cannot retrieve the last page number of a not yet executed Pager query' - ); - } - - protected function _setLastPage($page) - { - $this->_lastPage = $page; - - if ($this->getPage() > $page) { - $this->_setPage($page); - } - } - - public function getPage() - { - return $this->_page; - } - - public function getNextPage() - { - if ($this->getExecuted()) { - return min($this->getPage() + 1, $this->getLastPage()); - } - - throw new IPF_ORM_Exception( - 'Cannot retrieve the last page number of a not yet executed Pager query' - ); - } - - public function getPreviousPage() - { - if ($this->getExecuted()) { - return max($this->getPage() - 1, $this->getFirstPage()); - } - - throw new IPF_ORM_Exception( - 'Cannot retrieve the previous page number of a not yet executed Pager query' - ); - } - - public function getFirstIndice() - { - return ($this->getPage() - 1) * $this->getMaxPerPage() + 1; - } - - public function getLastIndice() - { - return min($this->getNumResults(), ($this->getPage() * $this->getMaxPerPage())); - } - - public function haveToPaginate() - { - if ($this->getExecuted()) { - return $this->getNumResults() > $this->getMaxPerPage(); - } - - throw new IPF_ORM_Exception( - 'Cannot know if it is necessary to paginate a not yet executed Pager query' - ); - } - - public function setPage($page) - { - $this->_setPage($page); - $this->_setExecuted(false); - } - - private function _setPage($page) - { - $page = intval($page); - $this->_page = ($page <= 0) ? 1 : $page; - } - - public function getMaxPerPage() - { - return $this->_maxPerPage; - } - - public function setMaxPerPage($max) - { - if ($max > 0) { - $this->_maxPerPage = $max; - } else if ($max == 0) { - $this->_maxPerPage = 25; - } else { - $this->_maxPerPage = abs($max); - } - - $this->_setExecuted(false); - } - - public function getResultsInPage() - { - $page = $this->getPage(); - - if ($page != $this->getLastPage()) { - return $this->getMaxPerPage(); - } - - $offset = ($this->getPage() - 1) * $this->getMaxPerPage(); - - return abs($this->getNumResults() - $offset); - } - - public function getQuery() - { - return $this->_query; - } - - protected function _setQuery($query) - { - if (is_string($query)) { - $query = IPF_ORM_Query::create()->parseQuery($query); - } - - $this->_query = $query; - } - - public function getCountQuery() - { - return ($this->_countQuery !== null) ? $this->_countQuery : $this->_query; - } - - public function setCountQuery($query, $params = null) - { - if (is_string($query)) { - $query = IPF_ORM_Query::create()->parseQuery($query); - } - - $this->_countQuery = $query; - - $this->setCountQueryParams($params); - - $this->_setExecuted(false); - } - - public function getCountQueryParams($defaultParams = array()) - { - return ($this->_countQueryParams !== null) ? $this->_countQueryParams : $defaultParams; - } - - public function setCountQueryParams($params = array(), $append = false) - { - if ($append && is_array($this->_countQueryParams)) { - $this->_countQueryParams = array_merge($this->_countQueryParams, $params); - } else { - if ($params !== null && !is_array($params)) { - $params = array($params); - } - - $this->_countQueryParams = $params; - } - - $this->_setExecuted(false); - } - - public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD) - { - if (!$this->getExecuted()) { - $this->_initialize($params); - } - return $this->getQuery()->execute($params, $hydrationMode); - } - - public function __debugInfo() - { - return array( - 'page' => $this->getPage(), - 'size' => $this->getMaxPerPage(), - 'query' => $this->getQuery(), - ); - } -} - diff --git a/ipf/legacy_orm/orm/pager/layout.php b/ipf/legacy_orm/orm/pager/layout.php deleted file mode 100644 index a5c1a32..0000000 --- a/ipf/legacy_orm/orm/pager/layout.php +++ /dev/null @@ -1,236 +0,0 @@ -_setPager($pager); - $this->_setPagerRange($pagerRange); - $this->_setUrlMask($urlMask); - - $this->setTemplate('[{%page}]'); - $this->setSelectedTemplate(''); - $this->setSeparatorTemplate(''); - } - - public function getPager() - { - return $this->_pager; - } - - protected function _setPager($pager) - { - $this->_pager = $pager; - } - - public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD) - { - return $this->getPager()->execute($params, $hydrationMode); - } - - public function getPagerRange() - { - return $this->_pagerRange; - } - - protected function _setPagerRange($pagerRange) - { - $this->_pagerRange = $pagerRange; - $this->getPagerRange()->setPager($this->getPager()); - } - - public function getUrlMask() - { - return $this->_urlMask; - } - - protected function _setUrlMask($urlMask) - { - $this->_urlMask = $urlMask; - } - - public function getTemplate() - { - return $this->_template; - } - - public function setTemplate($template) - { - $this->_template = $template; - } - - public function getSelectedTemplate() - { - return $this->_selectedTemplate; - } - - public function setSelectedTemplate($selectedTemplate) - { - $this->_selectedTemplate = $selectedTemplate; - } - - public function getSeparatorTemplate() - { - return $this->_separatorTemplate; - } - - public function setSeparatorTemplate($separatorTemplate) - { - $this->_separatorTemplate = $separatorTemplate; - } - - public function addMaskReplacement($oldMask, $newMask, $asValue = false) - { - if (($oldMask = trim($oldMask)) != 'page_number') { - $this->_maskReplacements[$oldMask] = array( - 'newMask' => $newMask, - 'asValue' => ($asValue === false) ? false : true - ); - } - } - - public function removeMaskReplacement($oldMask) - { - if (isset($this->_maskReplacements[$oldMask])) { - $this->_maskReplacements[$oldMask] = null; - unset($this->_maskReplacements[$oldMask]); - } - } - - public function cleanMaskReplacements() - { - $this->_maskReplacements = null; - $this->_maskReplacements = array(); - } - - public function display($options = array(), $return = true) - { - $range = $this->getPagerRange()->rangeAroundPage(); - $str = ''; - - // For each page in range - for ($i = 0, $l = count($range); $i < $l; $i++) { - // Define some optional mask values - $options['page_number'] = $range[$i]; - - $str .= $this->processPage($options); - - // Apply separator between pages - if ($i < $l - 1) { - $str .= $this->getSeparatorTemplate(); - } - } - - // Possible wish to return value instead of print it on screen - if ($return) { - return $str; - } - - echo $str; - } - - public function processPage($options = array()) - { - // Check if at least basic options are defined - if (!isset($options['page_number'])) { - throw new IPF_ORM_Exception( - 'Cannot process template of the given page. ' . - 'Missing at least one of needed parameters: \'page\' or \'page_number\'' - ); - - // Should never reach here - return ''; - } - - // Assign "page" options index if not defined yet - if (!isset($this->_maskReplacements['page']) && !isset($options['page'])) { - $options['page'] = $options['page_number']; - } - - return $this->_parseTemplate($options); - } - - public function __toString() - { - return $this->display(array(), true); - } - - protected function _parseTemplate($options = array()) - { - $str = $this->_parseUrlTemplate($options); - $replacements = $this->_parseReplacementsTemplate($options); - - return strtr($str, $replacements); - } - - protected function _parseUrlTemplate($options = array()) - { - $str = ''; - - // If given page is the current active one - if ($options['page_number'] == $this->getPager()->getPage()) { - $str = $this->_parseMaskReplacements($this->getSelectedTemplate()); - } - - // Possible attempt where Selected == Template - if ($str == '') { - $str = $this->_parseMaskReplacements($this->getTemplate()); - } - - return $str; - } - - protected function _parseReplacementsTemplate($options = array()) - { - // Defining "url" options index to allow {%url} mask - $options['url'] = $this->_parseUrl($options); - - $replacements = array(); - - foreach ($options as $k => $v) { - $replacements['{%'.$k.'}'] = $v; - } - - return $replacements; - } - - protected function _parseUrl($options = array()) - { - $str = $this->_parseMaskReplacements($this->getUrlMask()); - - $replacements = array(); - - foreach ($options as $k => $v) { - $replacements['{%'.$k.'}'] = $v; - } - - return strtr($str, $replacements); - } - - protected function _parseMaskReplacements($str) - { - $replacements = array(); - - foreach ($this->_maskReplacements as $k => $v) { - $replacements['{%'.$k.'}'] = ($v['asValue'] === true) ? $v['newMask'] : '{%'.$v['newMask'].'}'; - } - - return strtr($str, $replacements); - } - - public function __debugInfo() - { - return array( - 'pager' => $this->getPager() - ); - } -} - diff --git a/ipf/legacy_orm/orm/pager/layoutarrows.php b/ipf/legacy_orm/orm/pager/layoutarrows.php deleted file mode 100644 index 57c54d0..0000000 --- a/ipf/legacy_orm/orm/pager/layoutarrows.php +++ /dev/null @@ -1,64 +0,0 @@ -getPager(); - $str = ''; - - $range = $this->getPagerRange()->rangeAroundPage(); - - if ($pager->getFirstPage() != $pager->getLastPage()) - { - $this->removeMaskReplacement('page'); - - if ($range[0] > 1) - { - $options['page_number'] = 1; - $str .= $this->processPage($options); - - if ($range[0] > 2) - { - $options['page_number'] = 2; - $str .= $this->processPage($options); - } - - if ($range[0]>3) - $str .= ' ... '; - } - - // Pages listing - $str .= parent::display($options, true); - - $range = $this->getPagerRange()->rangeAroundPage(); - $last_range = $range[count($range)-1]; - - if ($last_range < $pager->getLastPage()) - { - if ($last_range < $pager->getLastPage() - 2) - { - $str .= ' ... '; - } - - if ($last_range < $pager->getLastPage() - 1) - { - $options['page_number'] = $pager->getLastPage() - 1; - $str .= $this->processPage($options); - } - - if ($last_range < $pager->getLastPage()) - { - $options['page_number'] = $pager->getLastPage(); - $str .= $this->processPage($options); - } - } - } - - if ($return) - return $str; - - echo $str; - } -} - diff --git a/ipf/legacy_orm/orm/pager/range.php b/ipf/legacy_orm/orm/pager/range.php deleted file mode 100644 index 18ffd8f..0000000 --- a/ipf/legacy_orm/orm/pager/range.php +++ /dev/null @@ -1,61 +0,0 @@ -_setOptions($options); - - if ($pager !== null) { - $this->setPager($pager); - } - } - - public function getPager() - { - return $this->pager; - } - - public function setPager($pager) - { - $this->pager = $pager; - - // Lazy-load initialization. It only should be called when all - // needed information data is ready (this can only happens when we have - // options stored and a IPF_ORM_Pager assocated) - $this->_initialize(); - } - - public function getOptions() - { - return $this->_options; - } - - public function getOption($option) - { - if (isset($this->_options[$option])) { - return $this->_options[$option]; - } - - throw new IPF_ORM_Exception( - 'Cannot access unexistent option \'' . $option . '\' in IPF_ORM_Pager_Range class' - ); - } - - protected function _setOptions($options) - { - $this->_options = $options; - } - - public function isInRange($page) - { - return (array_search($page, $this->rangeAroundPage()) !== false); - } - - abstract protected function _initialize(); - - abstract public function rangeAroundPage(); -} diff --git a/ipf/legacy_orm/orm/pager/range/jumping.php b/ipf/legacy_orm/orm/pager/range/jumping.php deleted file mode 100644 index 4324921..0000000 --- a/ipf/legacy_orm/orm/pager/range/jumping.php +++ /dev/null @@ -1,51 +0,0 @@ -_options['chunk'])) { - $this->_setChunkLength($this->_options['chunk']); - } else { - throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be define in options.'); - } - } - - public function getChunkLength() - { - return $this->_chunkLength; - } - - protected function _setChunkLength($chunkLength) - { - $this->_chunkLength = $chunkLength; - } - - public function rangeAroundPage() - { - $pager = $this->getPager(); - - if ($pager->getExecuted()) { - $page = $pager->getPage(); - - // Define initial assignments for StartPage and EndPage - $startPage = $page - ($page - 1) % $this->getChunkLength(); - $endPage = ($startPage + $this->getChunkLength()) - 1; - - // Check for EndPage out-range - if ($endPage > $pager->getLastPage()) { - $endPage = $pager->getLastPage(); - } - - // No need to check for out-range in start, it will never happens - - return range($startPage, $endPage); - } - - throw new IPF_ORM_Exception( - 'Cannot retrieve the range around the page of a not yet executed Pager query' - ); - } -} diff --git a/ipf/legacy_orm/orm/pager/range/sliding.php b/ipf/legacy_orm/orm/pager/range/sliding.php deleted file mode 100644 index cf8198e..0000000 --- a/ipf/legacy_orm/orm/pager/range/sliding.php +++ /dev/null @@ -1,66 +0,0 @@ -_options['chunk'])) { - $this->_setChunkLength($this->_options['chunk']); - } else { - throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be defined in options.'); - } - } - - public function getChunkLength() - { - return $this->_chunkLength; - } - - protected function _setChunkLength($chunkLength) - { - $chunkLength = (int) $chunkLength; - if (!$chunkLength) { - $chunkLength = 1; - } else { - $this->_chunkLength = $chunkLength; - } - } - - public function rangeAroundPage() - { - $pager = $this->getPager(); - - if ($pager->getExecuted()) { - $page = $pager->getPage(); - $pages = $pager->getLastPage(); - - $chunk = $this->getChunkLength(); - - if ($chunk > $pages) { - $chunk = $pages; - } - - $chunkStart = $page - (floor($chunk / 2)); - $chunkEnd = $page + (ceil($chunk / 2)-1); - - if ($chunkStart < 1) { - $adjust = 1 - $chunkStart; - $chunkStart = 1; - $chunkEnd = $chunkEnd + $adjust; - } - - if ($chunkEnd > $pages) { - $adjust = $chunkEnd - $pages; - $chunkStart = $chunkStart - $adjust; - $chunkEnd = $pages; - } - return range($chunkStart, $chunkEnd); - } - - throw new IPF_ORM_Exception( - 'Cannot retrieve the range around the page of a not yet executed Pager query' - ); - } -} diff --git a/ipf/legacy_orm/orm/query.php b/ipf/legacy_orm/orm/query.php deleted file mode 100644 index 1830c33..0000000 --- a/ipf/legacy_orm/orm/query.php +++ /dev/null @@ -1,1259 +0,0 @@ -_pendingJoinConditions = array(); - $this->_pendingSubqueries = array(); - $this->_pendingFields = array(); - $this->_neededTables = array(); - $this->_expressionMap = array(); - $this->_subqueryAliases = array(); - } - - public function createSubquery() - { - $class = get_class($this); - $obj = new $class(); - - // copy the aliases to the subquery - $obj->copyAliases($this); - - // this prevents the 'id' being selected, re ticket #307 - $obj->isSubquery(true); - - return $obj; - } - - protected function _addPendingJoinCondition($componentAlias, $joinCondition) - { - $this->_pendingJoins[$componentAlias] = $joinCondition; - } - - public function addEnumParam($key, $table = null, $column = null) - { - $array = (isset($table) || isset($column)) ? array($table, $column) : array(); - - if ($key === '?') { - $this->_enumParams[] = $array; - } else { - $this->_enumParams[$key] = $array; - } - } - - public function getEnumParams() - { - return $this->_enumParams; - } - - public function getDql() - { - $q = ''; - $q .= ( ! empty($this->_dqlParts['select']))? 'SELECT ' . implode(', ', $this->_dqlParts['select']) : ''; - $q .= ( ! empty($this->_dqlParts['from']))? ' FROM ' . implode(' ', $this->_dqlParts['from']) : ''; - $q .= ( ! empty($this->_dqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_dqlParts['where']) : ''; - $q .= ( ! empty($this->_dqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : ''; - $q .= ( ! empty($this->_dqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_dqlParts['having']) : ''; - $q .= ( ! empty($this->_dqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : ''; - $q .= ( ! empty($this->_dqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_dqlParts['limit']) : ''; - $q .= ( ! empty($this->_dqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_dqlParts['offset']) : ''; - - return $q; - } - - - public function fetchArray($params = array()) { - return $this->execute($params, IPF_ORM::HYDRATE_ARRAY); - } - - public function fetchOne($params = array(), $hydrationMode = null) - { - $collection = $this->execute($params, $hydrationMode); - - if (count($collection) === 0) { - return false; - } - - if ($collection instanceof IPF_ORM_Collection) { - return $collection->getFirst(); - } else if (is_array($collection)) { - return array_shift($collection); - } - - return false; - } - - public function isSubquery($bool = null) - { - if ($bool === null) { - return $this->_isSubquery; - } - - $this->_isSubquery = (bool) $bool; - return $this; - } - - public function getAggregateAlias($dqlAlias) - { - return $this->getSqlAggregateAlias($dqlAlias); - } - - public function getSqlAggregateAlias($dqlAlias) - { - if (isset($this->_aggregateAliasMap[$dqlAlias])) { - // mark the expression as used - $this->_expressionMap[$dqlAlias][1] = true; - - return $this->_aggregateAliasMap[$dqlAlias]; - } else if ( ! empty($this->_pendingAggregates)) { - $this->processPendingAggregates(); - - return $this->getSqlAggregateAlias($dqlAlias); - } else { - throw new IPF_ORM_Exception('Unknown aggregate alias: ' . $dqlAlias); - } - } - - public function getDqlPart($queryPart) - { - if ( ! isset($this->_dqlParts[$queryPart])) { - throw new IPF_ORM_Exception('Unknown query part ' . $queryPart); - } - - return $this->_dqlParts[$queryPart]; - } - - public function contains($dql) - { - return stripos($this->getDql(), $dql) === false ? false : true; - } - - public function processPendingFields($componentAlias) - { - $tableAlias = $this->getTableAlias($componentAlias); - $table = $this->_queryComponents[$componentAlias]['table']; - - if ( ! isset($this->_pendingFields[$componentAlias])) { - if ($this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) { - if ( ! $this->_isSubquery && $componentAlias == $this->getRootAlias()) { - throw new IPF_ORM_Exception("The root class of the query (alias $componentAlias) " - . " must have at least one field selected."); - } - } - return; - } - - // At this point we know the component is FETCHED (either it's the base class of - // the query (FROM xyz) or its a "fetch join"). - - // Check that the parent join (if there is one), is a "fetch join", too. - if (isset($this->_queryComponents[$componentAlias]['parent'])) { - $parentAlias = $this->_queryComponents[$componentAlias]['parent']; - if (is_string($parentAlias) && ! isset($this->_pendingFields[$parentAlias]) - && $this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) { - throw new IPF_ORM_Exception("The left side of the join between " - . "the aliases '$parentAlias' and '$componentAlias' must have at least" - . " the primary key field(s) selected."); - } - } - - $fields = $this->_pendingFields[$componentAlias]; - - // check for wildcards - if (in_array('*', $fields)) { - $fields = $table->getFieldNames(); - } else { - // only auto-add the primary key fields if this query object is not - // a subquery of another query object - if ( ! $this->_isSubquery || $this->_hydrator->getHydrationMode() === IPF_ORM::HYDRATE_NONE) { - $fields = array_unique(array_merge((array) $table->getIdentifier(), $fields)); - } - } - - $sql = array(); - foreach ($fields as $fieldName) { - $columnName = $table->getColumnName($fieldName); - $sql[] = $this->_conn->quoteIdentifier($tableAlias . '.' . $columnName) - . ' AS ' - . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName); - } - - $this->_neededTables[] = $tableAlias; - - return implode(', ', $sql); - } - - public function parseSelectField($field) - { - $terms = explode('.', $field); - - if (isset($terms[1])) { - $componentAlias = $terms[0]; - $field = $terms[1]; - } else { - reset($this->_queryComponents); - $componentAlias = key($this->_queryComponents); - $fields = $terms[0]; - } - - $tableAlias = $this->getTableAlias($componentAlias); - $table = $this->_queryComponents[$componentAlias]['table']; - - - // check for wildcards - if ($field === '*') { - $sql = array(); - - foreach ($table->getColumnNames() as $field) { - $sql[] = $this->parseSelectField($componentAlias . '.' . $field); - } - - return implode(', ', $sql); - } else { - $name = $table->getColumnName($field); - - $this->_neededTables[] = $tableAlias; - - return $this->_conn->quoteIdentifier($tableAlias . '.' . $name) - . ' AS ' - . $this->_conn->quoteIdentifier($tableAlias . '__' . $name); - } - } - - public function getExpressionOwner($expr) - { - if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') { - preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches); - - $match = current($matches); - - if (isset($match[0])) { - $terms = explode('.', $match[0]); - - return $terms[0]; - } - } - return $this->getRootAlias(); - - } - - public function parseSelect($dql) - { - $refs = $this->_tokenizer->sqlExplode($dql, ','); - - $pos = strpos(trim($refs[0]), ' '); - $first = substr($refs[0], 0, $pos); - - // check for DISTINCT keyword - if ($first === 'DISTINCT') { - $this->_sqlParts['distinct'] = true; - - $refs[0] = substr($refs[0], ++$pos); - } - - $parsedComponents = array(); - - foreach ($refs as $reference) { - $reference = trim($reference); - - if (empty($reference)) { - continue; - } - - $terms = $this->_tokenizer->sqlExplode($reference, ' '); - - $pos = strpos($terms[0], '('); - - if (count($terms) > 1 || $pos !== false) { - $expression = array_shift($terms); - $alias = array_pop($terms); - - if ( ! $alias) { - $alias = substr($expression, 0, $pos); - } - - $componentAlias = $this->getExpressionOwner($expression); - $expression = $this->parseClause($expression); - - $tableAlias = $this->getTableAlias($componentAlias); - - $index = count($this->_aggregateAliasMap); - - $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index); - - $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias; - - $this->_aggregateAliasMap[$alias] = $sqlAlias; - $this->_expressionMap[$alias][0] = $expression; - - $this->_queryComponents[$componentAlias]['agg'][$index] = $alias; - - $this->_neededTables[] = $tableAlias; - } else { - $e = explode('.', $terms[0]); - - if (isset($e[1])) { - $componentAlias = $e[0]; - $field = $e[1]; - } else { - reset($this->_queryComponents); - $componentAlias = key($this->_queryComponents); - $field = $e[0]; - } - - $this->_pendingFields[$componentAlias][] = $field; - } - } - } - - public function parseClause($clause) - { - $clause = trim($clause); - - if (is_numeric($clause)) { - return $clause; - } - - $terms = $this->_tokenizer->clauseExplode($clause, array(' ', '+', '-', '*', '/', '<', '>', '=', '>=', '<=')); - - $str = ''; - foreach ($terms as $term) { - $pos = strpos($term[0], '('); - - if ($pos !== false) { - $name = substr($term[0], 0, $pos); - $term[0] = $this->parseFunctionExpression($term[0]); - } else { - if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") { - - if (strpos($term[0], '.') !== false) { - if ( ! is_numeric($term[0])) { - $e = explode('.', $term[0]); - - $field = array_pop($e); - - if ($this->getType() === IPF_ORM_Query::SELECT) { - $componentAlias = implode('.', $e); - - if (empty($componentAlias)) { - $componentAlias = $this->getRootAlias(); - } - - $this->load($componentAlias); - - // check the existence of the component alias - if ( ! isset($this->_queryComponents[$componentAlias])) { - throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias); - } - - $table = $this->_queryComponents[$componentAlias]['table']; - - $def = $table->getDefinitionOf($field); - - // get the actual field name from alias - $field = $table->getColumnName($field); - - // check column existence - if ( ! $def) { - throw new IPF_ORM_Exception('Unknown column ' . $field); - } - - if (isset($def['owner'])) { - $componentAlias = $componentAlias . '.' . $def['owner']; - } - - $tableAlias = $this->getTableAlias($componentAlias); - - // build sql expression - $term[0] = $this->_conn->quoteIdentifier($tableAlias) - . '.' - . $this->_conn->quoteIdentifier($field); - } else { - // build sql expression - $field = $this->getRoot()->getColumnName($field); - $term[0] = $this->_conn->quoteIdentifier($field); - } - } - } else { - if ( ! empty($term[0]) && - ! in_array(strtoupper($term[0]), self::$_keywords) && - ! is_numeric($term[0])) { - - $componentAlias = $this->getRootAlias(); - - $found = false; - - if ($componentAlias !== false && - $componentAlias !== null) { - $table = $this->_queryComponents[$componentAlias]['table']; - - // check column existence - if ($table->hasField($term[0])) { - $found = true; - - $def = $table->getDefinitionOf($term[0]); - - // get the actual column name from field name - $term[0] = $table->getColumnName($term[0]); - - - if (isset($def['owner'])) { - $componentAlias = $componentAlias . '.' . $def['owner']; - } - - $tableAlias = $this->getTableAlias($componentAlias); - - if ($this->getType() === IPF_ORM_Query::SELECT) { - // build sql expression - $term[0] = $this->_conn->quoteIdentifier($tableAlias) - . '.' - . $this->_conn->quoteIdentifier($term[0]); - } else { - // build sql expression - $term[0] = $this->_conn->quoteIdentifier($term[0]); - } - } else { - $found = false; - } - } - - if ( ! $found) { - $term[0] = $this->getSqlAggregateAlias($term[0]); - } - } - } - } - } - - $str .= $term[0] . $term[1]; - } - return $str; - } - - public function parseIdentifierReference($expr) - { - } - - public function parseFunctionExpression($expr) - { - $pos = strpos($expr, '('); - - $name = substr($expr, 0, $pos); - - if ($name === '') { - return $this->parseSubquery($expr); - } - - $argStr = substr($expr, ($pos + 1), -1); - - $args = array(); - // parse args - - foreach ($this->_tokenizer->sqlExplode($argStr, ',') as $arg) { - $args[] = $this->parseClause($arg); - } - - // convert DQL function to its RDBMS specific equivalent - try { - $expr = call_user_func_array(array($this->_conn->expression, $name), $args); - } catch (IPF_ORM_Exception $e) { - throw new IPF_ORM_Exception('Unknown function ' . $name . '.'); - } - - return $expr; - } - - public function parseSubquery($subquery) - { - $trimmed = trim($this->_tokenizer->bracketTrim($subquery)); - - // check for possible subqueries - if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') { - // parse subquery - $trimmed = $this->createSubquery()->parseDqlQuery($trimmed)->getQuery(); - } else { - // parse normal clause - $trimmed = $this->parseClause($trimmed); - } - - return '(' . $trimmed . ')'; - } - - public function processPendingSubqueries() - { - foreach ($this->_pendingSubqueries as $value) { - list($dql, $alias) = $value; - - $subquery = $this->createSubquery(); - - $sql = $subquery->parseDqlQuery($dql, false)->getQuery(); - - reset($this->_queryComponents); - $componentAlias = key($this->_queryComponents); - $tableAlias = $this->getTableAlias($componentAlias); - - $sqlAlias = $tableAlias . '__' . count($this->_aggregateAliasMap); - - $this->_sqlParts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias); - - $this->_aggregateAliasMap[$alias] = $sqlAlias; - $this->_queryComponents[$componentAlias]['agg'][] = $alias; - } - $this->_pendingSubqueries = array(); - } - - public function processPendingAggregates() - { - // iterate trhough all aggregates - foreach ($this->_pendingAggregates as $aggregate) { - list ($expression, $components, $alias) = $aggregate; - - $tableAliases = array(); - - // iterate through the component references within the aggregate function - if ( ! empty ($components)) { - foreach ($components as $component) { - - if (is_numeric($component)) { - continue; - } - - $e = explode('.', $component); - - $field = array_pop($e); - $componentAlias = implode('.', $e); - - // check the existence of the component alias - if ( ! isset($this->_queryComponents[$componentAlias])) { - throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias); - } - - $table = $this->_queryComponents[$componentAlias]['table']; - - $field = $table->getColumnName($field); - - // check column existence - if ( ! $table->hasColumn($field)) { - throw new IPF_ORM_Exception('Unknown column ' . $field); - } - - $sqlTableAlias = $this->getSqlTableAlias($componentAlias); - - $tableAliases[$sqlTableAlias] = true; - - // build sql expression - - $identifier = $this->_conn->quoteIdentifier($sqlTableAlias . '.' . $field); - $expression = str_replace($component, $identifier, $expression); - } - } - - if (count($tableAliases) !== 1) { - $componentAlias = reset($this->_tableAliasMap); - $tableAlias = key($this->_tableAliasMap); - } - - $index = count($this->_aggregateAliasMap); - $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index); - - $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias; - - $this->_aggregateAliasMap[$alias] = $sqlAlias; - $this->_expressionMap[$alias][0] = $expression; - - $this->_queryComponents[$componentAlias]['agg'][$index] = $alias; - - $this->_neededTables[] = $tableAlias; - } - // reset the state - $this->_pendingAggregates = array(); - } - - protected function _buildSqlQueryBase() - { - switch ($this->_type) { - case self::DELETE: - $q = 'DELETE FROM '; - break; - case self::UPDATE: - $q = 'UPDATE '; - break; - case self::SELECT: - $distinct = ($this->_sqlParts['distinct']) ? 'DISTINCT ' : ''; - $q = 'SELECT ' . $distinct . implode(', ', $this->_sqlParts['select']) . ' FROM '; - break; - } - return $q; - } - - protected function _buildSqlFromPart() - { - $q = ''; - foreach ($this->_sqlParts['from'] as $k => $part) { - if ($k === 0) { - $q .= $part; - continue; - } - - // preserve LEFT JOINs only if needed - // Check if it's JOIN, if not add a comma separator instead of space - if (!preg_match('/\bJOIN\b/i', $part) && !isset($this->_pendingJoinConditions[$k])) { - $q .= ', ' . $part; - } else { - $e = explode(' ', $part); - - if (substr($part, 0, 9) === 'LEFT JOIN') { - $aliases = array_merge($this->_subqueryAliases, - array_keys($this->_neededTables)); - - if ( ! in_array($e[3], $aliases) && - ! in_array($e[2], $aliases) && - - ! empty($this->_pendingFields)) { - continue; - } - - } - - if (isset($this->_pendingJoinConditions[$k])) { - $parser = new IPF_ORM_JoinCondition($this, $this->_tokenizer); - - if (strpos($part, ' ON ') !== false) { - $part .= ' AND '; - } else { - $part .= ' ON '; - } - $part .= $parser->parse($this->_pendingJoinConditions[$k]); - - unset($this->_pendingJoinConditions[$k]); - } - - $tableAlias = trim($e[3], '"'); - $componentAlias = $this->getComponentAlias($tableAlias); - - $string = $this->getInheritanceCondition($componentAlias); - - if ($string) { - $q .= ' ' . $part . ' AND ' . $string; - } else { - $q .= ' ' . $part; - } - } - - $this->_sqlParts['from'][$k] = $part; - } - return $q; - } - - public function getSqlQuery($params = array()) - { - if ($this->_state !== self::STATE_DIRTY) { - return $this->_sql; - } - - // reset the state - if ( ! $this->isSubquery()) { - $this->_queryComponents = array(); - $this->_pendingAggregates = array(); - $this->_aggregateAliasMap = array(); - } - $this->reset(); - - // invoke the preQuery hook - $this->_preQuery(); - - // process the DQL parts => generate the SQL parts. - // this will also populate the $_queryComponents. - foreach ($this->_dqlParts as $queryPartName => $queryParts) { - $this->_processDqlQueryPart($queryPartName, $queryParts); - } - $this->_state = self::STATE_CLEAN; - - $params = $this->convertEnums($params); - - // Proceed with the generated SQL - - if (empty($this->_sqlParts['from'])) { - return false; - } - - $map = reset($this->_queryComponents); - $table = $map['table']; - $rootAlias = key($this->_queryComponents); - - $sql = array(); - if ( ! empty($this->_pendingFields)) { - foreach ($this->_queryComponents as $alias => $map) { - $fieldSql = $this->processPendingFields($alias); - if ( ! empty($fieldSql)) { - $sql[] = $fieldSql; - } - } - } - if ( ! empty($sql)) { - array_unshift($this->_sqlParts['select'], implode(', ', $sql)); - } - - $this->_pendingFields = array(); - - // build the basic query - $q = $this->_buildSqlQueryBase(); - $q .= $this->_buildSqlFromPart(); - - if ( ! empty($this->_sqlParts['set'])) { - $q .= ' SET ' . implode(', ', $this->_sqlParts['set']); - } - - $string = $this->getInheritanceCondition($this->getRootAlias()); - - // apply inheritance to WHERE part - if ( ! empty($string)) { - if (substr($string, 0, 1) === '(' && substr($string, -1) === ')') { - $this->_sqlParts['where'][] = $string; - } else { - $this->_sqlParts['where'][] = '(' . $string . ')'; - } - } - - $q .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : ''; - $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : ''; - $q .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']): ''; - $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : ''; - - $q = $this->_conn->modifyLimitQuery($q, $this->_sqlParts['limit'], $this->_sqlParts['offset']); - - // return to the previous state - if ( ! empty($string)) { - array_pop($this->_sqlParts['where']); - } - $this->_sql = $q; - - return $q; - } - - public function parseDqlQuery($query, $clear = true) - { - if ($clear) { - $this->clear(); - } - - $query = trim($query); - $query = str_replace("\r", "\n", str_replace("\r\n", "\n", $query)); - $query = str_replace("\n", ' ', $query); - - $parts = $this->_tokenizer->tokenizeQuery($query); - - foreach ($parts as $partName => $subParts) { - $subParts = trim($subParts); - $partName = strtolower($partName); - switch ($partName) { - case 'create': - $this->_type = self::CREATE; - break; - case 'insert': - $this->_type = self::INSERT; - break; - case 'delete': - $this->_type = self::DELETE; - break; - case 'select': - $this->_type = self::SELECT; - $this->_addDqlQueryPart($partName, $subParts); - break; - case 'update': - $this->_type = self::UPDATE; - $partName = 'from'; - case 'from': - $this->_addDqlQueryPart($partName, $subParts); - break; - case 'set': - $this->_addDqlQueryPart($partName, $subParts, true); - break; - case 'group': - case 'order': - $partName .= 'by'; - case 'where': - case 'having': - case 'limit': - case 'offset': - $this->_addDqlQueryPart($partName, $subParts); - break; - } - } - - return $this; - } - - public function load($path, $loadFields = true) - { - if (isset($this->_queryComponents[$path])) { - return $this->_queryComponents[$path]; - } - - $e = $this->_tokenizer->quoteExplode($path, ' INDEXBY '); - - $mapWith = null; - if (count($e) > 1) { - $mapWith = trim($e[1]); - - $path = $e[0]; - } - - // parse custom join conditions - $e = explode(' ON ', $path); - - $joinCondition = ''; - - if (count($e) > 1) { - $joinCondition = $e[1]; - $overrideJoin = true; - $path = $e[0]; - } else { - $e = explode(' WITH ', $path); - - if (count($e) > 1) { - $joinCondition = $e[1]; - $path = $e[0]; - } - $overrideJoin = false; - } - - $tmp = explode(' ', $path); - $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null; - - $e = preg_split("/[.:]/", $tmp[0], -1); - - $fullPath = $tmp[0]; - $prevPath = ''; - $fullLength = strlen($fullPath); - - if (isset($this->_queryComponents[$e[0]])) { - $table = $this->_queryComponents[$e[0]]['table']; - $componentAlias = $e[0]; - - $prevPath = $parent = array_shift($e); - } - - foreach ($e as $key => $name) { - // get length of the previous path - $length = strlen($prevPath); - - // build the current component path - $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name; - - $delimeter = substr($fullPath, $length, 1); - - // if an alias is not given use the current path as an alias identifier - if (strlen($prevPath) === $fullLength && isset($originalAlias)) { - $componentAlias = $originalAlias; - } else { - $componentAlias = $prevPath; - } - - // if the current alias already exists, skip it - if (isset($this->_queryComponents[$componentAlias])) { - throw new IPF_ORM_Exception("Duplicate alias '$componentAlias' in query."); - } - - if ( ! isset($table)) { - // process the root of the path - - $table = $this->loadRoot($name, $componentAlias); - } else { - $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN '; - - $relation = $table->getRelation($name); - $localTable = $table; - - $table = $relation->getTable(); - $this->_queryComponents[$componentAlias] = array('table' => $table, - 'parent' => $parent, - 'relation' => $relation, - 'map' => null); - - $localAlias = $this->getTableAlias($parent, $table->getTableName()); - $foreignAlias = $this->getTableAlias($componentAlias, $relation->getTable()->getTableName()); - - $foreignSql = $this->_conn->quoteIdentifier($relation->getTable()->getTableName()) - . ' ' - . $this->_conn->quoteIdentifier($foreignAlias); - - $map = $relation->getTable()->inheritanceMap; - - if ( ! $loadFields || ! empty($map) || $joinCondition) { - $this->_subqueryAliases[] = $foreignAlias; - } - - if ($relation instanceof IPF_ORM_Relation_Association) { - $asf = $relation->getAssociationTable(); - - $assocTableName = $asf->getTableName(); - - if ( ! $loadFields || ! empty($map) || $joinCondition) { - $this->_subqueryAliases[] = $assocTableName; - } - - $assocPath = $prevPath . '.' . $asf->getComponentName(); - - $this->_queryComponents[$assocPath] = array('parent' => $prevPath, 'relation' => $relation, 'table' => $asf); - - $assocAlias = $this->getTableAlias($assocPath, $asf->getTableName()); - - $queryPart = $join . $assocTableName . ' ' . $assocAlias; - - $queryPart .= ' ON ' . $localAlias - . '.' - . $localTable->getColumnName($localTable->getIdentifier()) // what about composite keys? - . ' = ' - . $assocAlias . '.' . $relation->getLocal(); - - if ($relation->isEqual()) { - // equal nest relation needs additional condition - $queryPart .= ' OR ' . $localAlias - . '.' - . $table->getColumnName($table->getIdentifier()) - . ' = ' - . $assocAlias . '.' . $relation->getForeign(); - } - - $this->_sqlParts['from'][] = $queryPart; - - $queryPart = $join . $foreignSql; - - if ( ! $overrideJoin) { - $queryPart .= $this->buildAssociativeRelationSql($relation, $assocAlias, $foreignAlias, $localAlias); - } - } else { - $queryPart = $this->buildSimpleRelationSql($relation, $foreignAlias, $localAlias, $overrideJoin, $join); - } - - $queryPart .= $this->buildInheritanceJoinSql($table->getComponentName(), $componentAlias); - - $this->_sqlParts['from'][$componentAlias] = $queryPart; - if ( ! empty($joinCondition)) { - $this->_pendingJoinConditions[$componentAlias] = $joinCondition; - } - } - if ($loadFields) { - - $restoreState = false; - // load fields if necessary - if ($loadFields && empty($this->_dqlParts['select'])) { - $this->_pendingFields[$componentAlias] = array('*'); - } - } - $parent = $prevPath; - } - - $table = $this->_queryComponents[$componentAlias]['table']; - - return $this->buildIndexBy($componentAlias, $mapWith); - } - - protected function buildSimpleRelationSql(IPF_ORM_Relation $relation, $foreignAlias, $localAlias, $overrideJoin, $join) - { - $queryPart = $join . $this->_conn->quoteIdentifier($relation->getTable()->getTableName()) - . ' ' - . $this->_conn->quoteIdentifier($foreignAlias); - - if ( ! $overrideJoin) { - $queryPart .= ' ON ' - . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocal()) - . ' = ' - . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeign()); - } - - return $queryPart; - } - - protected function buildIndexBy($componentAlias, $mapWith = null) - { - $table = $this->_queryComponents[$componentAlias]['table']; - - $indexBy = null; - - if (isset($mapWith)) { - $terms = explode('.', $mapWith); - - if (isset($terms[1])) { - $indexBy = $terms[1]; - } - } elseif ($table->getBoundQueryPart('indexBy') !== null) { - $indexBy = $table->getBoundQueryPart('indexBy'); - } - - if ($indexBy !== null) { - if ( ! $table->hasColumn($table->getColumnName($indexBy))) { - throw new IPF_ORM_Exception("Couldn't use key mapping. Column " . $indexBy . " does not exist."); - } - - $this->_queryComponents[$componentAlias]['map'] = $indexBy; - } - - return $this->_queryComponents[$componentAlias]; - } - - - protected function buildAssociativeRelationSql(IPF_ORM_Relation $relation, $assocAlias, $foreignAlias, $localAlias) - { - $table = $relation->getTable(); - - $queryPart = ' ON '; - - if ($relation->isEqual()) { - $queryPart .= '('; - } - - $localIdentifier = $table->getColumnName($table->getIdentifier()); - - $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier) - . ' = ' - . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeign()); - - if ($relation->isEqual()) { - $queryPart .= ' OR ' - . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier) - . ' = ' - . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocal()) - . ') AND ' - . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier) - . ' != ' - . $this->_conn->quoteIdentifier($localAlias . '.' . $localIdentifier); - } - - return $queryPart; - } - - public function loadRoot($name, $componentAlias) - { - // get the connection for the component - $manager = IPF_ORM_Manager::getInstance(); - - $table = $this->_conn->getTable($name); - $tableName = $table->getTableName(); - - // get the short alias for this table - $tableAlias = $this->getTableAlias($componentAlias, $tableName); - // quote table name - $queryPart = $this->_conn->quoteIdentifier($tableName); - - if ($this->_type === self::SELECT) { - $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias); - } - - $this->_tableAliasMap[$tableAlias] = $componentAlias; - - $queryPart .= $this->buildInheritanceJoinSql($name, $componentAlias); - - $this->_sqlParts['from'][] = $queryPart; - - $this->_queryComponents[$componentAlias] = array('table' => $table, 'map' => null); - - return $table; - } - - public function buildInheritanceJoinSql($name, $componentAlias) - { - // get the connection for the component - $manager = IPF_ORM_Manager::getInstance(); - - $table = $this->_conn->getTable($name); - $tableName = $table->getTableName(); - - // get the short alias for this table - $tableAlias = $this->getTableAlias($componentAlias, $tableName); - - return ''; - } - - public function getCountQuery() - { - // triggers dql parsing/processing - $this->getQuery(); // this is ugly - - // initialize temporary variables - $where = $this->_sqlParts['where']; - $having = $this->_sqlParts['having']; - $groupby = $this->_sqlParts['groupby']; - $map = reset($this->_queryComponents); - $componentAlias = key($this->_queryComponents); - $tableAlias = $this->_conn->quoteIdentifier($this->getTableAlias($componentAlias)); - $table = $map['table']; - - $idColumnNames = array(); - foreach ($table->getIdentifierColumnNames() as $column) { - $idColumnNames[] = $tableAlias . '.' . $this->_conn->quoteIdentifier($column); - } - - // build the query base - $q = 'SELECT COUNT(DISTINCT ' . implode(' || ', $idColumnNames) . ') AS num_results'; - - foreach ($this->_sqlParts['select'] as $field) { - if (strpos($field, '(') !== false) { - $q .= ', ' . $field; - } - } - - $q .= ' FROM ' . $this->_buildSqlFromPart(); - - // append column aggregation inheritance (if needed) - $string = $this->getInheritanceCondition($this->getRootAlias()); - - if ( ! empty($string)) { - $where[] = $string; - } - - // append conditions - $q .= ( ! empty($where)) ? ' WHERE ' . implode(' AND ', $where) : ''; - - if ( ! empty($groupby)) { - // Maintain existing groupby - $q .= ' GROUP BY ' . implode(', ', $groupby); - } else { - // Default groupby to primary identifier. Database defaults to this internally - // This is required for situations where the user has aggregate functions in the select part - // Without the groupby it fails - $q .= ' GROUP BY ' . implode(', ', $idColumnNames); - } - - $q .= ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having): ''; - - return $q; - } - - public function count($params = array()) - { - $q = $this->getCountQuery(); - - if ( ! is_array($params)) { - $params = array($params); - } - - $params = array_merge($this->_params['join'], $this->_params['where'], $this->_params['having'], $params); - - $params = $this->convertEnums($params); - - $results = $this->getConnection()->fetchAll($q, $params); - - if (count($results) > 1) { - $count = count($results); - } else { - if (isset($results[0])) { - $results[0] = array_change_key_case($results[0], CASE_LOWER); - $count = $results[0]['num_results']; - } else { - $count = 0; - } - } - - return (int) $count; - } - - public function query($query, $params = array(), $hydrationMode = null) - { - $this->parseDqlQuery($query); - return $this->execute($params, $hydrationMode); - } - - public function copy(IPF_ORM_Query $query = null) - { - if ( ! $query) { - $query = $this; - } - - $new = clone $query; - - return $new; - } - - public function __clone() - { - $this->_parsers = array(); - } - - public function free() - { - $this->reset(); - $this->_parsers = array(); - $this->_dqlParts = array(); - $this->_enumParams = array(); - } - - public function serialize() - { - $vars = get_object_vars($this); - } - - public function unserialize($serialized) - { - } -} diff --git a/ipf/legacy_orm/orm/query/abstract.php b/ipf/legacy_orm/orm/query/abstract.php deleted file mode 100644 index 6e0f48a..0000000 --- a/ipf/legacy_orm/orm/query/abstract.php +++ /dev/null @@ -1,998 +0,0 @@ - array(), - 'where' => array(), - 'set' => array(), - 'having' => array()); - - protected $_resultCache; - protected $_expireResultCache = false; - protected $_resultCacheTTL; - - protected $_queryCache; - protected $_expireQueryCache = false; - protected $_queryCacheTTL; - - protected $_conn; - - protected $_sqlParts = array( - 'select' => array(), - 'distinct' => false, - 'forUpdate' => false, - 'from' => array(), - 'set' => array(), - 'join' => array(), - 'where' => array(), - 'groupby' => array(), - 'having' => array(), - 'orderby' => array(), - 'limit' => false, - 'offset' => false, - ); - - protected $_dqlParts = array( - 'from' => array(), - 'select' => array(), - 'forUpdate' => false, - 'set' => array(), - 'join' => array(), - 'where' => array(), - 'groupby' => array(), - 'having' => array(), - 'orderby' => array(), - 'limit' => array(), - 'offset' => array(), - ); - - protected $_queryComponents = array(); - protected $_type = self::SELECT; - protected $_hydrator; - protected $_tokenizer; - protected $_parser; - protected $_tableAliasSeeds = array(); - protected $_options = array( - 'fetchMode' => IPF_ORM::FETCH_RECORD - ); - protected $_enumParams = array(); - - protected $_pendingSetParams = array(); - protected $_components; - protected $_preQueried = false; - - public function __construct(IPF_ORM_Connection $connection = null, - IPF_ORM_Hydrator_Abstract $hydrator = null) - { - if ($connection === null) { - $connection = IPF_ORM_Manager::connection(); - } - if ($hydrator === null) { - $hydrator = new IPF_ORM_Hydrator(); - } - $this->_conn = $connection; - $this->_hydrator = $hydrator; - $this->_tokenizer = new IPF_ORM_Query_Tokenizer(); - $this->_resultCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN); - $this->_queryCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN); - } - - public function setOption($name, $value) - { - if ( ! isset($this->_options[$name])) { - throw new IPF_ORM_Exception('Unknown option ' . $name); - } - $this->_options[$name] = $value; - } - - public function hasTableAlias($sqlTableAlias) - { - return $this->hasSqlTableAlias($sqlTableAlias); - } - - public function hasSqlTableAlias($sqlTableAlias) - { - return (isset($this->_tableAliasMap[$sqlTableAlias])); - } - - public function getTableAliases() - { - return $this->getTableAliasMap(); - } - - public function getTableAliasMap() - { - return $this->_tableAliasMap; - } - - public function getQueryPart($part) - { - return $this->getSqlQueryPart($part); - } - - public function getSqlQueryPart($part) - { - if ( ! isset($this->_sqlParts[$part])) { - throw new IPF_ORM_Exception('Unknown SQL query part ' . $part); - } - return $this->_sqlParts[$part]; - } - - public function setQueryPart($name, $part) - { - return $this->setSqlQueryPart($name, $part); - } - - public function setSqlQueryPart($name, $part) - { - if ( ! isset($this->_sqlParts[$name])) { - throw new IPF_ORM_Exception('Unknown query part ' . $name); - } - - if ($name !== 'limit' && $name !== 'offset') { - if (is_array($part)) { - $this->_sqlParts[$name] = $part; - } else { - $this->_sqlParts[$name] = array($part); - } - } else { - $this->_sqlParts[$name] = $part; - } - - return $this; - } - - public function addQueryPart($name, $part) - { - return $this->addSqlQueryPart($name, $part); - } - - public function addSqlQueryPart($name, $part) - { - if ( ! isset($this->_sqlParts[$name])) { - throw new IPF_ORM_Exception('Unknown query part ' . $name); - } - if (is_array($part)) { - $this->_sqlParts[$name] = array_merge($this->_sqlParts[$name], $part); - } else { - $this->_sqlParts[$name][] = $part; - } - return $this; - } - - public function removeQueryPart($name) - { - return $this->removeSqlQueryPart($name); - } - - public function removeSqlQueryPart($name) - { - if ( ! isset($this->_sqlParts[$name])) { - throw new IPF_ORM_Exception('Unknown query part ' . $name); - } - - if ($name == 'limit' || $name == 'offset') { - $this->_sqlParts[$name] = false; - } else { - $this->_sqlParts[$name] = array(); - } - - return $this; - } - - public function removeDqlQueryPart($name) - { - if ( ! isset($this->_dqlParts[$name])) { - throw new IPF_ORM_Exception('Unknown query part ' . $name); - } - - if ($name == 'limit' || $name == 'offset') { - $this->_dqlParts[$name] = false; - } else { - $this->_dqlParts[$name] = array(); - } - - return $this; - } - - public function getParams($params = array()) - { - return array_merge($this->_params['join'], $this->_params['set'], $this->_params['where'], $this->_params['having'], $params); - } - - public function setParams(array $params = array()) { - $this->_params = $params; - } - - public function setView(IPF_ORM_View $view) - { - $this->_view = $view; - } - - public function getView() - { - return $this->_view; - } - - public function convertEnums($params) - { - $table = $this->getRoot(); - - // $position tracks the position of the parameter, to ensure we're converting - // the right parameter value when simple ? placeholders are used. - // This only works because SET is only allowed in update statements and it's - // the first place where parameters can occur.. see issue #935 - $position = 0; - foreach ($this->_pendingSetParams as $fieldName => $value) { - $e = explode('.', $fieldName); - $fieldName = isset($e[1]) ? $e[1]:$e[0]; - if ($table->getTypeOf($fieldName) == 'enum') { - $value = $value === '?' ? $position : $value; - $this->addEnumParam($value, $table, $fieldName); - } - ++$position; - } - $this->_pendingSetParams = array(); - - foreach ($this->_enumParams as $key => $values) { - if (isset($params[$key])) { - if ( ! empty($values)) { - $params[$key] = $values[0]->enumIndex($values[1], $params[$key]); - } - } - } - - return $params; - } - - public function getInheritanceCondition($componentAlias) - { - $map = $this->_queryComponents[$componentAlias]['table']->inheritanceMap; - - // No inheritance map so lets just return - if (empty($map)) { - return; - } - - $tableAlias = $this->getSqlTableAlias($componentAlias); - - if ($this->_type !== IPF_ORM_Query::SELECT) { - $tableAlias = ''; - } else { - $tableAlias .= '.'; - } - - $field = key($map); - $value = current($map); - $identifier = $this->_conn->quoteIdentifier($tableAlias . $field); - - return $identifier . ' = ' . $this->_conn->quote($value);; - } - - public function getTableAlias($componentAlias, $tableName = null) - { - return $this->getSqlTableAlias($componentAlias, $tableName); - } - - public function getSqlTableAlias($componentAlias, $tableName = null) - { - $alias = array_search($componentAlias, $this->_tableAliasMap); - - if ($alias !== false) { - return $alias; - } - - if ($tableName === null) { - throw new IPF_ORM_Exception("Couldn't get short alias for " . $componentAlias); - } - - return $this->generateTableAlias($componentAlias, $tableName); - } - - public function generateNewTableAlias($oldAlias) - { - return $this->generateNewSqlTableAlias($oldAlias); - } - - public function generateNewSqlTableAlias($oldAlias) - { - if (isset($this->_tableAliasMap[$oldAlias])) { - // generate a new alias - $name = substr($oldAlias, 0, 1); - $i = ((int) substr($oldAlias, 1)); - - if ($i == 0) { - $i = 1; - } - - $newIndex = ($this->_tableAliasSeeds[$name] + $i); - - return $name . $newIndex; - } - - return $oldAlias; - } - - public function getTableAliasSeed($sqlTableAlias) - { - return $this->getSqlTableAliasSeed($sqlTableAlias); - } - - public function getSqlTableAliasSeed($sqlTableAlias) - { - if ( ! isset($this->_tableAliasSeeds[$sqlTableAlias])) { - return 0; - } - return $this->_tableAliasSeeds[$sqlTableAlias]; - } - - public function hasAliasDeclaration($componentAlias) - { - return isset($this->_queryComponents[$componentAlias]); - } - - public function getAliasDeclaration($componentAlias) - { - return $this->getQueryComponent($componentAlias); - } - - public function getQueryComponent($componentAlias) - { - if ( ! isset($this->_queryComponents[$componentAlias])) { - throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias); - } - - return $this->_queryComponents[$componentAlias]; - } - - public function copyAliases(IPF_ORM_Query_Abstract $query) - { - $this->_tableAliasMap = $query->_tableAliasMap; - $this->_queryComponents = $query->_queryComponents; - $this->_tableAliasSeeds = $query->_tableAliasSeeds; - return $this; - } - - public function getRootAlias() - { - if ( ! $this->_queryComponents) { - $this->getSql(); - } - reset($this->_queryComponents); - - return key($this->_queryComponents); - } - - public function getRootDeclaration() - { - $map = reset($this->_queryComponents); - return $map; - } - - public function getRoot() - { - $map = reset($this->_queryComponents); - - if ( ! isset($map['table'])) { - throw new IPF_ORM_Exception('Root component not initialized.'); - } - - return $map['table']; - } - - public function generateTableAlias($componentAlias, $tableName) - { - return $this->generateSqlTableAlias($componentAlias, $tableName); - } - - public function generateSqlTableAlias($componentAlias, $tableName) - { - preg_match('/([^_])/', $tableName, $matches); - $char = strtolower($matches[0]); - - $alias = $char; - - if ( ! isset($this->_tableAliasSeeds[$alias])) { - $this->_tableAliasSeeds[$alias] = 1; - } - - while (isset($this->_tableAliasMap[$alias])) { - if ( ! isset($this->_tableAliasSeeds[$alias])) { - $this->_tableAliasSeeds[$alias] = 1; - } - $alias = $char . ++$this->_tableAliasSeeds[$alias]; - } - - $this->_tableAliasMap[$alias] = $componentAlias; - - return $alias; - } - - public function getComponentAlias($sqlTableAlias) - { - if ( ! isset($this->_tableAliasMap[$sqlTableAlias])) { - throw new IPF_ORM_Exception('Unknown table alias ' . $sqlTableAlias); - } - return $this->_tableAliasMap[$sqlTableAlias]; - } - - protected function _execute($params) - { - $params = $this->_conn->convertBooleans($params); - - if ( ! $this->_view) { - if ($this->_queryCache || $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) { - $queryCacheDriver = $this->getQueryCacheDriver(); - // calculate hash for dql query - $dql = $this->getDql(); - $hash = md5($dql . 'IPF_ORM_QUERY_CACHE_SALT'); - $cached = $queryCacheDriver->fetch($hash); - if ($cached) { - $query = $this->_constructQueryFromCache($cached); - } else { - $query = $this->getSqlQuery($params); - $serializedQuery = $this->getCachedForm($query); - $queryCacheDriver->save($hash, $serializedQuery, $this->getQueryCacheLifeSpan()); - } - } else { - $query = $this->getSqlQuery($params); - } - $params = $this->convertEnums($params); - } else { - $query = $this->_view->getSelectSql(); - } - - if ($this->_type !== self::SELECT) { - return $this->_conn->exec($query, $params); - } - - $stmt = $this->_conn->execute($query, $params); - return $stmt; - } - - public function execute($params = array(), $hydrationMode = null) - { - $this->_preQuery(); - - if ($hydrationMode !== null) { - $this->_hydrator->setHydrationMode($hydrationMode); - } - - $params = $this->getParams($params); - - if ($this->_resultCache && $this->_type == self::SELECT) { - $cacheDriver = $this->getResultCacheDriver(); - - $dql = $this->getDql(); - // calculate hash for dql query - $hash = md5($dql . var_export($params, true)); - - $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash); - - if ($cached === false) { - // cache miss - $stmt = $this->_execute($params); - $this->_hydrator->setQueryComponents($this->_queryComponents); - $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap); - - $cached = $this->getCachedForm($result); - $cacheDriver->save($hash, $cached, $this->getResultCacheLifeSpan()); - } else { - $result = $this->_constructQueryFromCache($cached); - } - } else { - $stmt = $this->_execute($params); - - if (is_integer($stmt)) { - $result = $stmt; - } else { - $this->_hydrator->setQueryComponents($this->_queryComponents); - $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap); - } - } - - return $result; - } - - protected function _preQuery() - { - // Invoke preQuery() hook on IPF_ORM_Query for child classes which implement this hook - $this->preQuery(); - } - - public function preQuery() - { - } - - protected function _constructQueryFromCache($cached) - { - $cached = unserialize($cached); - $this->_tableAliasMap = $cached[2]; - $customComponent = $cached[0]; - - $queryComponents = array(); - $cachedComponents = $cached[1]; - foreach ($cachedComponents as $alias => $components) { - $e = explode('.', $components[0]); - if (count($e) === 1) { - $queryComponents[$alias]['table'] = $this->_conn->getTable($e[0]); - } else { - $queryComponents[$alias]['parent'] = $e[0]; - $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]); - $queryComponents[$alias]['table'] = $queryComponents[$alias]['relation']->getTable(); - } - if (isset($components[1])) { - $queryComponents[$alias]['agg'] = $components[1]; - } - if (isset($components[2])) { - $queryComponents[$alias]['map'] = $components[2]; - } - } - $this->_queryComponents = $queryComponents; - - return $customComponent; - } - - public function getCachedForm($customComponent = null) - { - $componentInfo = array(); - - foreach ($this->getQueryComponents() as $alias => $components) { - if ( ! isset($components['parent'])) { - $componentInfo[$alias][] = $components['table']->getComponentName(); - } else { - $componentInfo[$alias][] = $components['parent'] . '.' . $components['relation']->getAlias(); - } - if (isset($components['agg'])) { - $componentInfo[$alias][] = $components['agg']; - } - if (isset($components['map'])) { - $componentInfo[$alias][] = $components['map']; - } - } - - return serialize(array($customComponent, $componentInfo, $this->getTableAliasMap())); - } - - public function addSelect($select) - { - return $this->_addDqlQueryPart('select', $select, true); - } - - public function addTableAlias($tableAlias, $componentAlias) - { - return $this->addSqlTableAlias($tableAlias, $componentAlias); - } - - public function addSqlTableAlias($sqlTableAlias, $componentAlias) - { - $this->_tableAliasMap[$sqlTableAlias] = $componentAlias; - return $this; - } - - public function addFrom($from) - { - return $this->_addDqlQueryPart('from', $from, true); - } - - public function addWhere($where, $params = array()) - { - if (is_array($params)) { - $this->_params['where'] = array_merge($this->_params['where'], $params); - } else { - $this->_params['where'][] = $params; - } - return $this->_addDqlQueryPart('where', $where, true); - } - - public function whereIn($expr, $params = array(), $not = false) - { - $params = (array) $params; - - // if there's no params, return (else we'll get a WHERE IN (), invalid SQL) - if (!count($params)) - return $this; - - $a = array(); - foreach ($params as $k => $value) { - if ($value instanceof IPF_ORM_Expression) { - $value = $value->getSql(); - unset($params[$k]); - } else { - $value = '?'; - } - $a[] = $value; - } - - $this->_params['where'] = array_merge($this->_params['where'], $params); - - $where = $expr . ($not === true ? ' NOT ':'') . ' IN (' . implode(', ', $a) . ')'; - - return $this->_addDqlQueryPart('where', $where, true); - } - - public function whereNotIn($expr, $params = array()) - { - return $this->whereIn($expr, $params, true); - } - - public function addGroupBy($groupby) - { - return $this->_addDqlQueryPart('groupby', $groupby, true); - } - - public function addHaving($having, $params = array()) - { - if (is_array($params)) { - $this->_params['having'] = array_merge($this->_params['having'], $params); - } else { - $this->_params['having'][] = $params; - } - return $this->_addDqlQueryPart('having', $having, true); - } - - public function addOrderBy($orderby) - { - return $this->_addDqlQueryPart('orderby', $orderby, true); - } - - public function select($select) - { - return $this->_addDqlQueryPart('select', $select); - } - - public function distinct($flag = true) - { - $this->_sqlParts['distinct'] = (bool) $flag; - return $this; - } - - public function forUpdate($flag = true) - { - $this->_sqlParts['forUpdate'] = (bool) $flag; - return $this; - } - - public function delete() - { - $this->_type = self::DELETE; - return $this; - } - - public function update($update) - { - $this->_type = self::UPDATE; - return $this->_addDqlQueryPart('from', $update); - } - - public function set($key, $value, $params = null) - { - if (is_array($key)) { - foreach ($key as $k => $v) { - $this->set($k, '?', array($v)); - } - return $this; - } else { - if ($params !== null) { - if (is_array($params)) { - $this->_params['set'] = array_merge($this->_params['set'], $params); - } else { - $this->_params['set'][] = $params; - } - } - - $this->_pendingSetParams[$key] = $value; - - return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true); - } - } - - public function from($from) - { - return $this->_addDqlQueryPart('from', $from); - } - - public function innerJoin($join, $params = array()) - { - if (is_array($params)) { - $this->_params['join'] = array_merge($this->_params['join'], $params); - } else { - $this->_params['join'][] = $params; - } - - return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true); - } - - public function leftJoin($join, $params = array()) - { - if (is_array($params)) { - $this->_params['join'] = array_merge($this->_params['join'], $params); - } else { - $this->_params['join'][] = $params; - } - - return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true); - } - - public function groupBy($groupby) - { - return $this->_addDqlQueryPart('groupby', $groupby); - } - - public function where($where, $params = array()) - { - $this->_params['where'] = array(); - if (is_array($params)) { - $this->_params['where'] = $params; - } else { - $this->_params['where'][] = $params; - } - - return $this->_addDqlQueryPart('where', $where); - } - - public function having($having, $params = array()) - { - $this->_params['having'] = array(); - if (is_array($params)) { - $this->_params['having'] = $params; - } else { - $this->_params['having'][] = $params; - } - - return $this->_addDqlQueryPart('having', $having); - } - - public function orderBy($orderby) - { - return $this->_addDqlQueryPart('orderby', $orderby); - } - - public function limit($limit) - { - return $this->_addDqlQueryPart('limit', $limit); - } - - public function offset($offset) - { - return $this->_addDqlQueryPart('offset', $offset); - } - - public function getSql($params = array()) - { - return $this->getSqlQuery($params); - } - - protected function clear() - { - $this->_sqlParts = array( - 'select' => array(), - 'distinct' => false, - 'forUpdate' => false, - 'from' => array(), - 'set' => array(), - 'join' => array(), - 'where' => array(), - 'groupby' => array(), - 'having' => array(), - 'orderby' => array(), - 'limit' => false, - 'offset' => false, - ); - } - - public function setHydrationMode($hydrationMode) - { - $this->_hydrator->setHydrationMode($hydrationMode); - return $this; - } - - public function getAliasMap() - { - return $this->_queryComponents; - } - - public function getQueryComponents() - { - return $this->_queryComponents; - } - - public function getParts() - { - return $this->getSqlParts(); - } - - public function getSqlParts() - { - return $this->_sqlParts; - } - - public function getType() - { - return $this->_type; - } - - public function useCache($driver = true, $timeToLive = null) - { - return $this->useResultCache($driver, $timeToLive); - } - - public function useResultCache($driver = true, $timeToLive = null) - { - if ($driver !== null && $driver !== true && ! ($driver instanceOf IPF_ORM_Cache_Interface)) { - $msg = 'First argument should be instance of IPF_ORM_Cache_Interface or null.'; - throw new IPF_ORM_Exception($msg); - } - $this->_resultCache = $driver; - - return $this->setResultCacheLifeSpan($timeToLive); - } - - public function useQueryCache(IPF_ORM_Cache_Interface $driver, $timeToLive = null) - { - $this->_queryCache = $driver; - return $this->setQueryCacheLifeSpan($timeToLive); - } - - public function expireCache($expire = true) - { - return $this->expireResultCache($expire); - } - - public function expireResultCache($expire = true) - { - $this->_expireResultCache = true; - return $this; - } - - public function expireQueryCache($expire = true) - { - $this->_expireQueryCache = true; - return $this; - } - - public function setCacheLifeSpan($timeToLive) - { - return $this->setResultCacheLifeSpan($timeToLive); - } - - public function setResultCacheLifeSpan($timeToLive) - { - if ($timeToLive !== null) { - $timeToLive = (int) $timeToLive; - } - $this->_resultCacheTTL = $timeToLive; - - return $this; - } - - public function getResultCacheLifeSpan() - { - return $this->_resultCacheTTL; - } - - public function setQueryCacheLifeSpan($timeToLive) - { - if ($timeToLive !== null) { - $timeToLive = (int) $timeToLive; - } - $this->_queryCacheTTL = $timeToLive; - - return $this; - } - - public function getQueryCacheLifeSpan() - { - return $this->_queryCacheTTL; - } - - public function getCacheDriver() - { - return $this->getResultCacheDriver(); - } - - public function getResultCacheDriver() - { - if ($this->_resultCache instanceof IPF_ORM_Cache_Interface) { - return $this->_resultCache; - } else { - return $this->_conn->getResultCacheDriver(); - } - } - - public function getQueryCacheDriver() - { - if ($this->_queryCache instanceof IPF_ORM_Cache_Interface) { - return $this->_queryCache; - } else { - return $this->_conn->getQueryCacheDriver(); - } - } - - public function getConnection() - { - return $this->_conn; - } - - protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false) - { - if ($append) { - $this->_dqlParts[$queryPartName][] = $queryPart; - } else { - $this->_dqlParts[$queryPartName] = array($queryPart); - } - - $this->_state = IPF_ORM_Query::STATE_DIRTY; - return $this; - } - - protected function _processDqlQueryPart($queryPartName, $queryParts) - { - $this->removeSqlQueryPart($queryPartName); - - if (is_array($queryParts) && ! empty($queryParts)) { - foreach ($queryParts as $queryPart) { - $parser = $this->_getParser($queryPartName); - $sql = $parser->parse($queryPart); - if (isset($sql)) { - if ($queryPartName == 'limit' || $queryPartName == 'offset') { - $this->setSqlQueryPart($queryPartName, $sql); - } else { - $this->addSqlQueryPart($queryPartName, $sql); - } - } - } - } - } - - protected function _getParser($name) - { - if ( ! isset($this->_parsers[$name])) { - $class = 'IPF_ORM_Query_' . ucwords(strtolower($name)); - - //IPF_ORM::autoload($class); - - if ( ! class_exists($class)) { - throw new IPF_ORM_Exception('Unknown parser ' . $name); - } - - $this->_parsers[$name] = new $class($this, $this->_tokenizer); - } - - return $this->_parsers[$name]; - } - - abstract public function getSqlQuery($params = array()); - - abstract public function parseDqlQuery($query); - - public function parseQuery($query) - { - return $this->parseDqlQuery($query); - } - - public function getQuery($params = array()) - { - return $this->getSqlQuery($params); - } - - public function __debugInfo() - { - return array('sql' => $this->getQuery()); - } -} - diff --git a/ipf/legacy_orm/orm/query/check.php b/ipf/legacy_orm/orm/query/check.php deleted file mode 100644 index 79728c3..0000000 --- a/ipf/legacy_orm/orm/query/check.php +++ /dev/null @@ -1,99 +0,0 @@ -getTable($table); - } - $this->table = $table; - $this->_tokenizer = new IPF_ORM_Query_Tokenizer(); - } - - public function getTable() - { - return $this->table; - } - - public function parse($dql) - { - $this->sql = $this->parseClause($dql); - } - - public function parseClause($dql) - { - $parts = $this->_tokenizer->sqlExplode($dql, ' AND '); - - if (count($parts) > 1) { - $ret = array(); - foreach ($parts as $part) { - $ret[] = $this->parseSingle($part); - } - - $r = implode(' AND ', $ret); - } else { - $parts = $this->_tokenizer->quoteExplode($dql, ' OR '); - if (count($parts) > 1) { - $ret = array(); - foreach ($parts as $part) { - $ret[] = $this->parseClause($part); - } - - $r = implode(' OR ', $ret); - } else { - $ret = $this->parseSingle($dql); - return $ret; - } - } - return '(' . $r . ')'; - } - - public function parseSingle($part) - { - $e = explode(' ', $part); - - $e[0] = $this->parseFunction($e[0]); - - switch ($e[1]) { - case '>': - case '<': - case '=': - case '!=': - case '<>': - - break; - default: - throw new IPF_ORM_Exception('Unknown operator ' . $e[1]); - } - - return implode(' ', $e); - } - public function parseFunction($dql) - { - if (($pos = strpos($dql, '(')) !== false) { - $func = substr($dql, 0, $pos); - $value = substr($dql, ($pos + 1), -1); - - $expr = $this->table->getConnection()->expression; - - if ( ! method_exists($expr, $func)) { - throw new IPF_ORM_Exception('Unknown function ' . $func); - } - - $func = $expr->$func($value); - } - return $func; - } - - public function getSql() - { - return $this->sql; - } -} diff --git a/ipf/legacy_orm/orm/query/condition.php b/ipf/legacy_orm/orm/query/condition.php deleted file mode 100644 index b18e9eb..0000000 --- a/ipf/legacy_orm/orm/query/condition.php +++ /dev/null @@ -1,81 +0,0 @@ -_tokenizer->bracketExplode($str, array(' \&\& ', ' AND '), '(', ')'); - - if (count($parts) > 1) { - $ret = array(); - foreach ($parts as $part) { - $part = $this->_tokenizer->bracketTrim($part, '(', ')'); - $ret[] = $this->parse($part); - } - $r = implode(' AND ', $ret); - } else { - - $parts = $this->_tokenizer->bracketExplode($str, array(' \|\| ', ' OR '), '(', ')'); - if (count($parts) > 1) { - $ret = array(); - foreach ($parts as $part) { - $part = $this->_tokenizer->bracketTrim($part, '(', ')'); - $ret[] = $this->parse($part); - } - $r = implode(' OR ', $ret); - } else { - // Fix for #710 - if (substr($parts[0],0,1) == '(' && substr($parts[0], -1) == ')') { - return $this->parse(substr($parts[0], 1, -1)); - } else { - // Processing NOT here - if (strtoupper(substr($parts[0], 0, 4)) === 'NOT ') { - $r = 'NOT ('.$this->parse(substr($parts[0], 4)).')'; - } else { - return $this->load($parts[0]); - } - } - } - } - - return '(' . $r . ')'; - } - - private function parseBoolean($value) - { - // parse booleans - if ($value == 'true') { - $value = 1; - } elseif ($value == 'false') { - $value = 0; - } - return $value; - } - - public function parseLiteralValue($value) - { - // check that value isn't a string - if (strpos($value, '\'') === false) { - // parse booleans - $value = $this->parseBoolean($value); - - $a = explode('.', $value); - - if (count($a) > 1) { - // either a float or a component.. - - if ( ! is_numeric($a[0])) { - // a component found - $field = array_pop($a); - $reference = implode('.', $a); - $value = $this->query->getTableAlias($reference). '.' . $field; - } - } - } else { - // string literal found - } - return $value; - } -} diff --git a/ipf/legacy_orm/orm/query/filter.php b/ipf/legacy_orm/orm/query/filter.php deleted file mode 100644 index 65b0917..0000000 --- a/ipf/legacy_orm/orm/query/filter.php +++ /dev/null @@ -1,12 +0,0 @@ -_filters[] = $filter; - } - - public function get($key) - { - if ( ! isset($this->_filters[$key])) { - throw new IPF_ORM_Exception('Unknown filter ' . $key); - } - return $this->_filters[$key]; - } - - public function set($key, IPF_ORM_Query_Filter $listener) - { - $this->_filters[$key] = $listener; - } - - public function preQuery(IPF_ORM_Query $query) - { - foreach ($this->_filters as $filter) { - $filter->preQuery($query); - } - } - - public function postQuery(IPF_ORM_Query $query) - { - foreach ($this->_filters as $filter) { - $filter->postQuery($query); - } - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/query/filter/interface.php b/ipf/legacy_orm/orm/query/filter/interface.php deleted file mode 100644 index 09c104f..0000000 --- a/ipf/legacy_orm/orm/query/filter/interface.php +++ /dev/null @@ -1,7 +0,0 @@ -_tokenizer->bracketExplode($str, 'JOIN'); - - $from = $return ? array() : null; - - $operator = false; - - switch (trim($parts[0])) { - case 'INNER': - $operator = ':'; - case 'LEFT': - array_shift($parts); - break; - } - - $last = ''; - - foreach ($parts as $k => $part) { - $part = trim($part); - - if (empty($part)) { - continue; - } - - $e = explode(' ', $part); - - if (end($e) == 'INNER' || end($e) == 'LEFT') { - $last = array_pop($e); - } - $part = implode(' ', $e); - - foreach ($this->_tokenizer->bracketExplode($part, ',') as $reference) { - $reference = trim($reference); - $e = explode(' ', $reference); - $e2 = explode('.', $e[0]); - - if ($operator) { - $e[0] = array_shift($e2) . $operator . implode('.', $e2); - } - - if ($return) { - $from[] = $e; - } else { - $table = $this->query->load(implode(' ', $e)); - } - } - - $operator = ($last == 'INNER') ? ':' : '.'; - } - return $from; - } -} diff --git a/ipf/legacy_orm/orm/query/groupby.php b/ipf/legacy_orm/orm/query/groupby.php deleted file mode 100644 index 824f11c..0000000 --- a/ipf/legacy_orm/orm/query/groupby.php +++ /dev/null @@ -1,15 +0,0 @@ -query->parseClause($reference); - } - return implode(', ', $r); - } -} diff --git a/ipf/legacy_orm/orm/query/having.php b/ipf/legacy_orm/orm/query/having.php deleted file mode 100644 index 3fe3b6d..0000000 --- a/ipf/legacy_orm/orm/query/having.php +++ /dev/null @@ -1,58 +0,0 @@ -_tokenizer->bracketExplode($func, ',', '(', ')'); - - foreach ($params as $k => $param) { - $params[$k] = $this->parseAggregateFunction($param); - } - - $funcs = $name . '(' . implode(', ', $params) . ')'; - - return $funcs; - - } else { - if ( ! is_numeric($func)) { - $a = explode('.', $func); - - if (count($a) > 1) { - $field = array_pop($a); - $reference = implode('.', $a); - $map = $this->query->load($reference, false); - $field = $map['table']->getColumnName($field); - $func = $this->query->getTableAlias($reference) . '.' . $field; - } else { - $field = end($a); - $func = $this->query->getAggregateAlias($field); - } - return $func; - } else { - return $func; - } - } - } - - final public function load($having) - { - $tokens = $this->_tokenizer->bracketExplode($having, ' ', '(', ')'); - $part = $this->parseAggregateFunction(array_shift($tokens)); - $operator = array_shift($tokens); - $value = implode(' ', $tokens); - $part .= ' ' . $operator . ' ' . $value; - // check the RHS for aggregate functions - if (strpos($value, '(') !== false) { - $value = $this->parseAggregateFunction($value); - } - return $part; - } -} diff --git a/ipf/legacy_orm/orm/query/joincondition.php b/ipf/legacy_orm/orm/query/joincondition.php deleted file mode 100644 index 94d969b..0000000 --- a/ipf/legacy_orm/orm/query/joincondition.php +++ /dev/null @@ -1,126 +0,0 @@ -_tokenizer->sqlExplode($condition); - - if (count($e) > 2) { - $expr = new IPF_ORM_Expression($e[0], $this->query->getConnection()); - $e[0] = $expr->getSql(); - - $operator = $e[1]; - - if (substr(trim($e[2]), 0, 1) != '(') { - $expr = new IPF_ORM_Expression($e[2], $this->query->getConnection()); - $e[2] = $expr->getSql(); - } - - // We need to check for agg functions here - $hasLeftAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[0], $leftMatches); - - if ($hasLeftAggExpression) { - $e[0] = $leftMatches[2]; - } - - $hasRightAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[2], $rightMatches); - - if ($hasRightAggExpression) { - $e[2] = $rightMatches[2]; - } - - $a = explode('.', $e[0]); - $field = array_pop($a); - $reference = implode('.', $a); - $value = $e[2]; - - $conn = $this->query->getConnection(); - $alias = $this->query->getTableAlias($reference); - $map = $this->query->getAliasDeclaration($reference); - $table = $map['table']; - // check if value is enumerated value - $enumIndex = $table->enumIndex($field, trim($value, "'")); - - if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { - $enumIndex = $conn->quote($enumIndex, 'text'); - } - - // FIX: Issues with "(" XXX ")" - if ($hasRightAggExpression) { - $value = '(' . $value . ')'; - } - - if (substr($value, 0, 1) == '(') { - // trim brackets - $trimmed = $this->_tokenizer->bracketTrim($value); - - if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') { - // subquery found - $q = $this->query->createSubquery(); - - // Change due to bug "(" XXX ")" - //$value = '(' . $q->parseQuery($trimmed)->getQuery() . ')'; - $value = $q->parseQuery($trimmed)->getQuery(); - } elseif (substr($trimmed, 0, 4) == 'SQL:') { - // Change due to bug "(" XXX ")" - //$value = '(' . substr($trimmed, 4) . ')'; - $value = substr($trimmed, 4); - } else { - // simple in expression found - $e = $this->_tokenizer->sqlExplode($trimmed, ','); - - $value = array(); - foreach ($e as $part) { - $index = $table->enumIndex($field, trim($part, "'")); - - if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { - $index = $conn->quote($index, 'text'); - } - - if ($index !== false) { - $value[] = $index; - } else { - $value[] = $this->parseLiteralValue($part); - } - } - - // Change due to bug "(" XXX ")" - //$value = '(' . implode(', ', $value) . ')'; - $value = implode(', ', $value); - } - } else { - if ($enumIndex !== false) { - $value = $enumIndex; - } else { - $value = $this->parseLiteralValue($value); - } - } - - switch ($operator) { - case '<': - case '>': - case '=': - case '!=': - if ($enumIndex !== false) { - $value = $enumIndex; - } - default: - $leftExpr = (($hasLeftAggExpression) ? $leftMatches[1] . '(' : '') - . $alias . '.' . $field - . (($hasLeftAggExpression) ? $leftMatches[3] . ')' : '') ; - - $rightExpr = (($hasRightAggExpression) ? $rightMatches[1] . '(' : '') - . $value - . (($hasRightAggExpression) ? $rightMatches[3] . ')' : '') ; - - $condition = $leftExpr . ' ' . $operator . ' ' . $rightExpr; - } - - } - - return $condition; - } -} diff --git a/ipf/legacy_orm/orm/query/limit.php b/ipf/legacy_orm/orm/query/limit.php deleted file mode 100644 index f0cd5ae..0000000 --- a/ipf/legacy_orm/orm/query/limit.php +++ /dev/null @@ -1,9 +0,0 @@ -query->parseClause($r); - - $ret[] = $r; - } - return $ret; - } -} diff --git a/ipf/legacy_orm/orm/query/parser.php b/ipf/legacy_orm/orm/query/parser.php deleted file mode 100644 index 71d6f1b..0000000 --- a/ipf/legacy_orm/orm/query/parser.php +++ /dev/null @@ -1,5 +0,0 @@ -query = $query; - if ( ! $tokenizer) { - $tokenizer = new IPF_ORM_Query_Tokenizer(); - } - $this->_tokenizer = $tokenizer; - } - - public function getQuery() - { - return $this->query; - } -} diff --git a/ipf/legacy_orm/orm/query/registry.php b/ipf/legacy_orm/orm/query/registry.php deleted file mode 100644 index f35ff42..0000000 --- a/ipf/legacy_orm/orm/query/registry.php +++ /dev/null @@ -1,39 +0,0 @@ -_queries[$key] = $query; - } else { - // namespace found - $e = explode('/', $key); - - $this->_queries[$e[0]][$e[1]] = $query; - } - } - - public function get($key, $namespace = null) - { - if (isset($namespace)) { - if ( ! isset($this->_queries[$namespace][$key])) { - throw new IPF_ORM_Exception('A query with the name ' . $namespace . '/' . $key . ' does not exist.'); - } - $query = $this->_queries[$namespace][$key]; - } else { - if ( ! isset($this->_queries[$key])) { - throw new IPF_ORM_Exception('A query with the name ' . $key . ' does not exist.'); - } - $query = $this->_queries[$key]; - } - - if ( ! ($query instanceof IPF_ORM_Query)) { - $query = IPF_ORM_Query::create()->parseQuery($query); - } - - return $query; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/query/select.php b/ipf/legacy_orm/orm/query/select.php deleted file mode 100644 index 0994a68..0000000 --- a/ipf/legacy_orm/orm/query/select.php +++ /dev/null @@ -1,9 +0,0 @@ -query->parseSelect($dql); - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/query/set.php b/ipf/legacy_orm/orm/query/set.php deleted file mode 100644 index 5e26e3b..0000000 --- a/ipf/legacy_orm/orm/query/set.php +++ /dev/null @@ -1,27 +0,0 @@ -_tokenizer->sqlExplode($dql, ' '); - foreach ($terms as $term) { - preg_match_all("/[a-z0-9_]+\.[a-z0-9_]+[\.[a-z0-9]+]*/i", $term, $m); - - if (isset($m[0])) { - foreach ($m[0] as $part) { - $e = explode('.', trim($part)); - $field = array_pop($e); - - $reference = implode('.', $e); - - $alias = $this->query->getTableAlias($reference); - $map = $this->query->getAliasDeclaration($reference); - - $dql = str_replace($part, $map['table']->getColumnName($field), $dql); - } - } - } - return $dql; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/query/tokenizer.php b/ipf/legacy_orm/orm/query/tokenizer.php deleted file mode 100644 index f9a7d13..0000000 --- a/ipf/legacy_orm/orm/query/tokenizer.php +++ /dev/null @@ -1,235 +0,0 @@ -sqlExplode($query, ' '); - - foreach ($tokens as $index => $token) { - $token = trim($token); - switch (strtolower($token)) { - case 'delete': - case 'update': - case 'select': - case 'set': - case 'from': - case 'where': - case 'limit': - case 'offset': - case 'having': - $p = $token; - //$parts[$token] = array(); - $parts[$token] = ''; - break; - case 'order': - case 'group': - $i = ($index + 1); - if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') { - $p = $token; - $parts[$token] = ''; - //$parts[$token] = array(); - } else { - $parts[$p] .= "$token "; - //$parts[$p][] = $token; - } - break; - case 'by': - continue; - default: - if ( ! isset($p)) { - throw new IPF_ORM_Exception( - "Couldn't tokenize query. Encountered invalid token: '$token'."); - } - - $parts[$p] .= "$token "; - //$parts[$p][] = $token; - } - } - return $parts; - } - - public function bracketTrim($str, $e1 = '(', $e2 = ')') - { - if (substr($str, 0, 1) === $e1 && substr($str, -1) === $e2) { - return substr($str, 1, -1); - } else { - return $str; - } - } - - public function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')') - { - if (is_array($d)) { - $a = preg_split('#('.implode('|', $d).')#i', $str); - $d = stripslashes($d[0]); - } else { - $a = explode($d, $str); - } - - $i = 0; - $term = array(); - foreach($a as $key=>$val) { - if (empty($term[$i])) { - $term[$i] = trim($val); - $s1 = substr_count($term[$i], $e1); - $s2 = substr_count($term[$i], $e2); - - if ($s1 == $s2) { - $i++; - } - } else { - $term[$i] .= $d . trim($val); - $c1 = substr_count($term[$i], $e1); - $c2 = substr_count($term[$i], $e2); - - if ($c1 == $c2) { - $i++; - } - } - } - return $term; - } - - public function quoteExplode($str, $d = ' ') - { - if (is_array($d)) { - $a = preg_split('/('.implode('|', $d).')/', $str); - $d = stripslashes($d[0]); - } else { - $a = explode($d, $str); - } - - $i = 0; - $term = array(); - foreach ($a as $key => $val) { - if (empty($term[$i])) { - $term[$i] = trim($val); - - if ( ! (substr_count($term[$i], "'") & 1)) { - $i++; - } - } else { - $term[$i] .= $d . trim($val); - - if ( ! (substr_count($term[$i], "'") & 1)) { - $i++; - } - } - } - return $term; - } - - public function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')') - { - if ($d == ' ') { - $d = array(' ', '\s'); - } - if (is_array($d)) { - $d = array_map('preg_quote', $d); - - if (in_array(' ', $d)) { - $d[] = '\s'; - } - - $split = '#(' . implode('|', $d) . ')#'; - - $str = preg_split($split, $str); - $d = stripslashes($d[0]); - } else { - $str = explode($d, $str); - } - - $i = 0; - $term = array(); - - foreach ($str as $key => $val) { - if (empty($term[$i])) { - $term[$i] = trim($val); - - $s1 = substr_count($term[$i], $e1); - $s2 = substr_count($term[$i], $e2); - - if (strpos($term[$i], '(') !== false) { - if ($s1 == $s2) { - $i++; - } - } else { - if ( ! (substr_count($term[$i], "'") & 1) && - ! (substr_count($term[$i], "\"") & 1)) { - $i++; - } - } - } else { - $term[$i] .= $d . trim($val); - $c1 = substr_count($term[$i], $e1); - $c2 = substr_count($term[$i], $e2); - - if (strpos($term[$i], '(') !== false) { - if ($c1 == $c2) { - $i++; - } - } else { - if ( ! (substr_count($term[$i], "'") & 1) && - ! (substr_count($term[$i], "\"") & 1)) { - $i++; - } - } - } - } - return $term; - } - - public function clauseExplode($str, array $d, $e1 = '(', $e2 = ')') - { - if (is_array($d)) { - $d = array_map('preg_quote', $d); - - if (in_array(' ', $d)) { - $d[] = '\s'; - } - - $split = '#(' . implode('|', $d) . ')#'; - - $str = preg_split($split, $str, -1, PREG_SPLIT_DELIM_CAPTURE); - } - - $i = 0; - $term = array(); - - foreach ($str as $key => $val) { - if ($key & 1) { - if (isset($term[($i - 1)]) && ! is_array($term[($i - 1)])) { - $term[($i - 1)] = array($term[($i - 1)], $val); - } - continue; - } - if (empty($term[$i])) { - $term[$i] = $val; - } else { - $term[$i] .= $str[($key - 1)] . $val; - } - - $c1 = substr_count($term[$i], $e1); - $c2 = substr_count($term[$i], $e2); - - if (strpos($term[$i], '(') !== false) { - if ($c1 == $c2) { - $i++; - } - } else { - if ( ! (substr_count($term[$i], "'") & 1) && - ! (substr_count($term[$i], "\"") & 1)) { - $i++; - } - } - } - - if (isset($term[$i - 1])) { - $term[$i - 1] = array($term[$i - 1], ''); - } - - return $term; - } -} diff --git a/ipf/legacy_orm/orm/query/where.php b/ipf/legacy_orm/orm/query/where.php deleted file mode 100644 index 1f51bbc..0000000 --- a/ipf/legacy_orm/orm/query/where.php +++ /dev/null @@ -1,143 +0,0 @@ -_tokenizer->bracketTrim(trim($where)); - $conn = $this->query->getConnection(); - $terms = $this->_tokenizer->sqlExplode($where); - - if (count($terms) > 1) { - if (substr($where, 0, 6) == 'EXISTS') { - return $this->parseExists($where, true); - } elseif (substr($where, 0, 10) == 'NOT EXISTS') { - return $this->parseExists($where, false); - } - } - - if (count($terms) < 3) { - $terms = $this->_tokenizer->sqlExplode($where, array('=', '<', '<>', '>', '!=')); - } - - if (count($terms) > 1) { - $first = array_shift($terms); - $value = array_pop($terms); - $operator = trim(substr($where, strlen($first), -strlen($value))); - $table = null; - $field = null; - - if (strpos($first, "'") === false && strpos($first, '(') === false) { - // normal field reference found - $a = explode('.', $first); - - $field = array_pop($a); - $reference = implode('.', $a); - - if (empty($reference)) { - $map = $this->query->getRootDeclaration(); - - $alias = $this->query->getTableAlias($this->query->getRootAlias()); - $table = $map['table']; - } else { - $map = $this->query->load($reference, false); - - $alias = $this->query->getTableAlias($reference); - $table = $map['table']; - } - } - $first = $this->query->parseClause($first); - - $sql = $first . ' ' . $operator . ' ' . $this->parseValue($value, $table, $field); - - return $sql; - } else { - return $where; - } - } - - public function parseValue($value, IPF_ORM_Table $table = null, $field = null) - { - $conn = $this->query->getConnection(); - - if (substr($value, 0, 1) == '(') { - // trim brackets - $trimmed = $this->_tokenizer->bracketTrim($value); - - if (substr($trimmed, 0, 4) == 'FROM' || - substr($trimmed, 0, 6) == 'SELECT') { - - // subquery found - $q = new IPF_ORM_Query(); - $value = '(' . $this->query->createSubquery()->parseQuery($trimmed, false)->getQuery() . ')'; - - } elseif (substr($trimmed, 0, 4) == 'SQL:') { - $value = '(' . substr($trimmed, 4) . ')'; - } else { - // simple in expression found - $e = $this->_tokenizer->sqlExplode($trimmed, ','); - - $value = array(); - - $index = false; - - foreach ($e as $part) { - if (isset($table) && isset($field)) { - $index = $table->enumIndex($field, trim($part, "'")); - - if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { - $index = $conn->quote($index, 'text'); - } - } - - if ($index !== false) { - $value[] = $index; - } else { - $value[] = $this->parseLiteralValue($part); - } - } - - $value = '(' . implode(', ', $value) . ')'; - } - } else if (substr($value, 0, 1) == ':' || $value === '?') { - // placeholder found - if (isset($table) && isset($field) && $table->getTypeOf($field) == 'enum') { - $this->query->addEnumParam($value, $table, $field); - } else { - $this->query->addEnumParam($value, null, null); - } - } else { - $enumIndex = false; - if (isset($table) && isset($field)) { - // check if value is enumerated value - $enumIndex = $table->enumIndex($field, trim($value, "'")); - - if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { - $enumIndex = $conn->quote($enumIndex, 'text'); - } - } - - if ($enumIndex !== false) { - $value = $enumIndex; - } else { - $value = $this->parseLiteralValue($value); - } - } - return $value; - } - - public function parseExists($where, $negation) - { - $operator = ($negation) ? 'EXISTS' : 'NOT EXISTS'; - - $pos = strpos($where, '('); - - if ($pos == false) { - throw new IPF_ORM_Exception('Unknown expression, expected a subquery with () -marks'); - } - - $sub = $this->_tokenizer->bracketTrim(substr($where, $pos)); - - return $operator . ' (' . $this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')'; - } -} diff --git a/ipf/legacy_orm/orm/rawsql.php b/ipf/legacy_orm/orm/rawsql.php deleted file mode 100644 index fce5016..0000000 --- a/ipf/legacy_orm/orm/rawsql.php +++ /dev/null @@ -1,233 +0,0 @@ -_parseSelectFields($queryPart, $append); - return $this; - } - - if (!isset($this->parts[$queryPartName])) { - $this->_sqlParts[$queryPartName] = array(); - } - - if (!$append) { - $this->_sqlParts[$queryPartName] = array($queryPart); - } else { - $this->_sqlParts[$queryPartName][] = $queryPart; - } - return $this; - } - - protected function _addDqlQueryPart($queryPartName, $queryPart, $append=false) - { - return $this->parseDqlQueryPart($queryPartName, $queryPart, $append); - } - - private function _parseSelectFields($queryPart, $append=false) - { - if ($append) - $this->fields[] = $queryPart; - else - $this->fields = array($queryPart); - - $this->_sqlParts['select'] = array(); - } - - public function parseDqlQuery($query) - { - $this->_parseSelectFields($query); - $this->clear(); - - $tokens = $this->_tokenizer->sqlExplode($query, ' '); - - $parts = array(); - foreach ($tokens as $key => $part) { - $partLowerCase = strtolower($part); - switch ($partLowerCase) { - case 'select': - case 'from': - case 'where': - case 'limit': - case 'offset': - case 'having': - $type = $partLowerCase; - if ( ! isset($parts[$partLowerCase])) { - $parts[$partLowerCase] = array(); - } - break; - case 'order': - case 'group': - $i = $key + 1; - if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') { - $type = $partLowerCase . 'by'; - $parts[$type] = array(); - } else { - //not a keyword so we add it to the previous type - $parts[$type][] = $part; - } - break; - case 'by': - continue; - default: - //not a keyword so we add it to the previous type. - if ( ! isset($parts[$type][0])) { - $parts[$type][0] = $part; - } else { - // why does this add to index 0 and not append to the - // array. If it had done that one could have used - // parseQueryPart. - $parts[$type][0] .= ' '.$part; - } - } - } - - $this->_sqlParts = $parts; - $this->_sqlParts['select'] = array(); - - return $this; - } - - public function getSqlQuery($params=array()) - { - $select = array(); - - foreach ($this->fields as $field) { - if (preg_match('/^{([^}{]+)\.([^}{]+)}$/U', $field, $e)) { - // try to auto-add component - if (!$this->hasSqlTableAlias($e[1])) { - try { - $this->addComponent($e[1], ucwords($e[1])); - } catch (IPF_ORM_Exception $exception) { - throw new IPF_ORM_Exception('The associated component for table alias ' . $e[1] . ' couldn\'t be found.'); - } - } - - $componentAlias = $this->getComponentAlias($e[1]); - - if ($e[2] == '*') { - foreach ($this->_queryComponents[$componentAlias]['table']->getColumnNames() as $name) { - $field = $e[1] . '.' . $name; - - $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $name; - } - } else { - $field = $e[1] . '.' . $e[2]; - $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $e[2]; - } - } else { - $select['__raw__'][] = $field; - } - } - - // force-add all primary key fields - - foreach ($this->getTableAliasMap() as $tableAlias => $componentAlias) { - $map = $this->_queryComponents[$componentAlias]; - - foreach ((array) $map['table']->getIdentifierColumnNames() as $key) { - $field = $tableAlias . '.' . $key; - - if ( ! isset($this->_sqlParts['select'][$field])) { - $select[$componentAlias][$field] = $field . ' AS ' . $tableAlias . '__' . $key; - } - } - } - - // first add the fields of the root component - reset($this->_queryComponents); - $componentAlias = key($this->_queryComponents); - - $q = 'SELECT ' . implode(', ', $select[$componentAlias]); - unset($select[$componentAlias]); - - foreach ($select as $component => $fields) { - if ( ! empty($fields)) { - $q .= ', ' . implode(', ', $fields); - } - } - - $string = $this->getInheritanceCondition($this->getRootAlias()); - if ( ! empty($string)) { - $this->_sqlParts['where'][] = $string; - } - $copy = $this->_sqlParts; - unset($copy['select']); - - $q .= ( ! empty($this->_sqlParts['from']))? ' FROM ' . implode(' ', $this->_sqlParts['from']) : ''; - $q .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : ''; - $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : ''; - $q .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']) : ''; - $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : ''; - $q .= ( ! empty($this->_sqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_sqlParts['limit']) : ''; - $q .= ( ! empty($this->_sqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_sqlParts['offset']) : ''; - - if ( ! empty($string)) { - array_pop($this->_sqlParts['where']); - } - return $q; - } - - public function getFields() - { - return $this->fields; - } - - public function addComponent($tableAlias, $path) - { - $tmp = explode(' ', $path); - $originalAlias = (count($tmp) > 1) ? end($tmp) : null; - - $e = explode('.', $tmp[0]); - - $fullPath = $tmp[0]; - $fullLength = strlen($fullPath); - - $table = null; - - $currPath = ''; - - if (isset($this->_queryComponents[$e[0]])) { - $table = $this->_queryComponents[$e[0]]['table']; - $currPath = $parent = array_shift($e); - } - - foreach ($e as $k => $component) { - // get length of the previous path - $length = strlen($currPath); - - // build the current component path - $currPath = ($currPath) ? $currPath . '.' . $component : $component; - - $delimeter = substr($fullPath, $length, 1); - - // if an alias is not given use the current path as an alias identifier - if (strlen($currPath) === $fullLength && isset($originalAlias)) { - $componentAlias = $originalAlias; - } else { - $componentAlias = $currPath; - } - - if (!isset($table)) { - $table = IPF_ORM_Manager::connection()->getTable($component); - $this->_queryComponents[$componentAlias] = array('table' => $table); - } else { - $relation = $table->getRelation($component); - - $this->_queryComponents[$componentAlias] = array('table' => $relation->getTable(), - 'parent' => $parent, - 'relation' => $relation); - } - $this->addSqlTableAlias($tableAlias, $componentAlias); - - $parent = $currPath; - } - - return $this; - } -} - diff --git a/ipf/legacy_orm/orm/record.php b/ipf/legacy_orm/orm/record.php deleted file mode 100644 index 8be51ab..0000000 --- a/ipf/legacy_orm/orm/record.php +++ /dev/null @@ -1,1212 +0,0 @@ -_table = $table; - $exists = ( ! $isNewEntry); - } else { - // get the table of this class - $class = get_class($this); - $this->_table = IPF_ORM::getTable($class); - $exists = false; - } - - // Check if the current connection has the records table in its registry - // If not this record is only used for creating table definition and setting up - // relations. - if ( ! $this->_table->getConnection()->hasTable($this->_table->getComponentName())) { - return; - } - - $this->_oid = self::$_index; - - self::$_index++; - - // get the data array - $this->_data = $this->_table->getData(); - - // get the column count - $count = count($this->_data); - - $this->_values = $this->cleanData($this->_data); - - $this->prepareIdentifiers($exists); - - if ( ! $exists) { - if ($count > count($this->_values)) { - $this->_state = IPF_ORM_Record::STATE_TDIRTY; - } else { - $this->_state = IPF_ORM_Record::STATE_TCLEAN; - } - - // set the default values for this record - $this->assignDefaultValues(); - } else { - $this->_state = IPF_ORM_Record::STATE_CLEAN; - - if ($count < $this->_table->getColumnCount()) { - $this->_state = IPF_ORM_Record::STATE_PROXY; - } - } - - $repository = $this->_table->getRepository(); - $repository->add($this); - - $this->construct(); - } - - public static function _index() - { - return self::$_index; - } - - public function construct(){} - - public function getOid() - { - return $this->_oid; - } - - public function oid() - { - return $this->_oid; - } - - public function isValid() - { - // Clear the stack from any previous errors. - $this->_errors = array(); - - // Run validation process - $validator = new IPF_ORM_Validator(); - $validator->validateRecord($this); - $this->validate(); - if ($this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) { - $this->validateOnInsert(); - } else { - $this->validateOnUpdate(); - } - - return count($this->_errors) === 0; - } - - public function addError($invalidFieldName, $errorCode = 'general') - { - $this->_errors[$invalidFieldName][] = $errorCode; - } - - public function getErrors() - { - return $this->_errors; - } - - protected function validate(){} - - protected function validateOnUpdate(){} - - protected function validateOnInsert(){} - - public function preSerialize($event){} - - public function postSerialize($event){} - - public function preUnserialize($event){} - - public function postUnserialize($event){} - - public function preSave($event){} - - public function postSave($event){} - - public function preDelete($event){} - - public function postDelete($event){} - - public function preUpdate($event){} - - public function postUpdate($event){} - - public function preInsert($event){} - - public function postInsert($event){} - - public function assignDefaultValues($overwrite = false) - { - if ( ! $this->_table->hasDefaultValues()) { - return false; - } - foreach ($this->_data as $column => $value) { - $default = $this->_table->getDefaultValueOf($column); - - if ($default === null) { - continue; - } - - if (IPF_ORM_Null::isNull($value) || $overwrite) { - $this->_data[$column] = $default; - $this->_modified[] = $column; - $this->_state = IPF_ORM_Record::STATE_TDIRTY; - } - } - } - - public function cleanData(&$data) - { - $tmp = $data; - $data = array(); - - foreach ($this->getTable()->getFieldNames() as $fieldName) { - if (isset($tmp[$fieldName])) { - $data[$fieldName] = $tmp[$fieldName]; - } else if (array_key_exists($fieldName, $tmp)) { - $data[$fieldName] = IPF_ORM_Null::getInstance(); - } else if (!isset($this->_data[$fieldName])) { - $data[$fieldName] = IPF_ORM_Null::getInstance(); - } - unset($tmp[$fieldName]); - } - - return $tmp; - } - - public function hydrate(array $data) - { - $this->_values = array_merge($this->_values, $this->cleanData($data)); - $this->_data = array_merge($this->_data, $data); - $this->prepareIdentifiers(true); - } - - private function prepareIdentifiers($exists = true) - { - switch ($this->_table->getIdentifierType()) { - case IPF_ORM::IDENTIFIER_AUTOINC: - case IPF_ORM::IDENTIFIER_NATURAL: - $name = $this->_table->getIdentifier(); - if (is_array($name)) { - $name = $name[0]; - } - if ($exists) { - if (isset($this->_data[$name]) && !IPF_ORM_Null::isNull($this->_data[$name])) { - $this->_id[$name] = $this->_data[$name]; - } - } - break; - case IPF_ORM::IDENTIFIER_COMPOSITE: - $names = $this->_table->getIdentifier(); - - foreach ($names as $name) { - if (IPF_ORM_Null::isNull($this->_data[$name])) { - $this->_id[$name] = null; - } else { - $this->_id[$name] = $this->_data[$name]; - } - } - break; - } - } - - public function serialize() - { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_SERIALIZE); - - $this->preSerialize($event); - - $vars = get_object_vars($this); - - unset($vars['_references']); - unset($vars['_table']); - unset($vars['_errors']); - unset($vars['_filter']); - - $name = $this->_table->getIdentifier(); - $this->_data = array_merge($this->_data, $this->_id); - - foreach ($this->_data as $k => $v) { - if ($v instanceof IPF_ORM_Record && $this->_table->getTypeOf($k) != 'object') { - unset($vars['_data'][$k]); - } elseif (IPF_ORM_Null::isNull($v)) { - unset($vars['_data'][$k]); - } else { - switch ($this->_table->getTypeOf($k)) { - case 'array': - case 'object': - $vars['_data'][$k] = serialize($vars['_data'][$k]); - break; - case 'gzip': - $vars['_data'][$k] = gzcompress($vars['_data'][$k]); - break; - case 'enum': - $vars['_data'][$k] = $this->_table->enumIndex($k, $vars['_data'][$k]); - break; - } - } - } - - $str = serialize($vars); - - $this->postSerialize($event); - - return $str; - } - - public function unserialize($serialized) - { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_UNSERIALIZE); - - $this->preUnserialize($event); - - $connection = IPF_ORM_Manager::connection(); - - $this->_oid = self::$_index; - self::$_index++; - - $this->_table = $connection->getTable(get_class($this)); - - $array = unserialize($serialized); - - foreach($array as $k => $v) { - $this->$k = $v; - } - - foreach ($this->_data as $k => $v) { - switch ($this->_table->getTypeOf($k)) { - case 'array': - case 'object': - $this->_data[$k] = unserialize($this->_data[$k]); - break; - case 'gzip': - $this->_data[$k] = gzuncompress($this->_data[$k]); - break; - case 'enum': - $this->_data[$k] = $this->_table->enumValue($k, $this->_data[$k]); - break; - - } - } - - $this->_table->getRepository()->add($this); - - $this->cleanData($this->_data); - - $this->prepareIdentifiers($this->exists()); - - $this->postUnserialize($event); - } - - public function state($state = null) - { - if ($state == null) { - return $this->_state; - } - - $err = false; - if (is_integer($state)) { - if ($state >= 1 && $state <= 6) { - $this->_state = $state; - } else { - $err = true; - } - } else if (is_string($state)) { - $upper = strtoupper($state); - - $const = 'IPF_ORM_Record::STATE_' . $upper; - if (defined($const)) { - $this->_state = constant($const); - } else { - $err = true; - } - } - - if ($this->_state === IPF_ORM_Record::STATE_TCLEAN || - $this->_state === IPF_ORM_Record::STATE_CLEAN) { - $this->_modified = array(); - } - - if ($err) { - throw new IPF_ORM_Exception('Unknown record state ' . $state); - } - } - - public function refresh($deep = false) - { - $id = $this->identifier(); - if ( ! is_array($id)) { - $id = array($id); - } - if (empty($id)) { - return false; - } - $id = array_values($id); - - if ($deep) { - $query = $this->getTable()->createQuery(); - foreach (array_keys($this->_references) as $name) { - $query->leftJoin(get_class($this) . '.' . $name); - } - $query->where(implode(' = ? AND ', $this->getTable()->getIdentifierColumnNames()) . ' = ?'); - $this->clearRelated(); - $record = $query->fetchOne($id); - } else { - // Use FETCH_ARRAY to avoid clearing object relations - $record = $this->getTable()->find($id, IPF_ORM::HYDRATE_ARRAY); - if ($record) { - $this->hydrate($record); - } - } - - if ($record === false) { - throw new IPF_ORM_Exception('Failed to refresh. Record does not exist.'); - } - - $this->_modified = array(); - - $this->prepareIdentifiers(); - - $this->_state = IPF_ORM_Record::STATE_CLEAN; - - return $this; - } - - public function refreshRelated($name = null) - { - if (is_null($name)) { - foreach ($this->_table->getRelations() as $rel) { - $this->_references[$rel->getAlias()] = $rel->fetchRelatedFor($this); - } - } else { - $rel = $this->_table->getRelation($name); - $this->_references[$name] = $rel->fetchRelatedFor($this); - } - } - - public function clearRelated() - { - $this->_references = array(); - } - - public function getTable() - { - return $this->_table; - } - - public function getData() - { - return $this->_data; - } - - public function rawGet($fieldName) - { - if ( ! isset($this->_data[$fieldName])) { - throw new IPF_ORM_Exception('Unknown property '. $fieldName); - } - if (IPF_ORM_Null::isNull($this->_data[$fieldName])) { - return null; - } - - return $this->_data[$fieldName]; - } - - public function load() - { - // only load the data from database if the IPF_ORM_Record is in proxy state - if ($this->_state == IPF_ORM_Record::STATE_PROXY) { - $this->refresh(); - $this->_state = IPF_ORM_Record::STATE_CLEAN; - return true; - } - return false; - } - - public function get($fieldName, $load = true) - { - $value = IPF_ORM_Null::getInstance(); - - if (isset($this->_data[$fieldName])) { - if (IPF_ORM_Null::isNull($this->_data[$fieldName]) && $load) { - $this->load(); - } - if (IPF_ORM_Null::isNull($this->_data[$fieldName])) { - $value = null; - } else { - $value = $this->_data[$fieldName]; - } - return $value; - } - - if (isset($this->_values[$fieldName])) { - return $this->_values[$fieldName]; - } - - if (!isset($this->_references[$fieldName]) && $load) { - $rel = $this->_table->getRelation($fieldName); - $this->_references[$fieldName] = $rel->fetchRelatedFor($this); - } - return $this->_references[$fieldName]; - } - - public function mapValue($name, $value) - { - $this->_values[$name] = $value; - } - - public function set($fieldName, $value, $load = true) - { - if (isset($this->_data[$fieldName])) { - $type = $this->_table->getTypeOf($fieldName); - if ($value instanceof IPF_ORM_Record) { - $id = $value->getIncremented(); - - if ($id !== null && $type !== 'object') { - $value = $id; - } - } - - if ($load) { - $old = $this->get($fieldName, $load); - } else { - $old = $this->_data[$fieldName]; - } - - if ($this->_isValueModified($type, $old, $value)) { - if ($value === null) { - $value = IPF_ORM_Null::getInstance(); - } - - $this->_data[$fieldName] = $value; - $this->_modified[] = $fieldName; - switch ($this->_state) { - case IPF_ORM_Record::STATE_CLEAN: - $this->_state = IPF_ORM_Record::STATE_DIRTY; - break; - case IPF_ORM_Record::STATE_TCLEAN: - $this->_state = IPF_ORM_Record::STATE_TDIRTY; - break; - } - } - } else { - $this->coreSetRelated($fieldName, $value); - } - - return $this; - } - - protected function _isValueModified($type, $old, $new) - { - if ($type == 'boolean' && (is_bool($old) || is_numeric($old)) && (is_bool($new) || is_numeric($new)) && $old == $new) { - return false; - } else { - return $old !== $new; - } - } - - public function coreSetRelated($name, $value) - { - $rel = $this->_table->getRelation($name); - - if ($value === null) { - $value = IPF_ORM_Null::getInstance(); - } - - // one-to-many or one-to-one relation - if ($rel instanceof IPF_ORM_Relation_ForeignKey || $rel instanceof IPF_ORM_Relation_LocalKey) { - if ( ! $rel->isOneToOne()) { - // one-to-many relation found - if ( ! ($value instanceof IPF_ORM_Collection)) { - throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting one-to-many references."); - } - if (isset($this->_references[$name])) { - $this->_references[$name]->setData($value->getData()); - return $this; - } - } else { - if (!IPF_ORM_Null::isNull($value)) { - $relatedTable = $value->getTable(); - $foreignFieldName = $relatedTable->getFieldName($rel->getForeign()); - $localFieldName = $this->_table->getFieldName($rel->getLocal()); - - // one-to-one relation found - if ( ! ($value instanceof IPF_ORM_Record)) { - throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Record or IPF_ORM_Null when setting one-to-one references."); - } - if ($rel instanceof IPF_ORM_Relation_LocalKey) { - if ( ! empty($foreignFieldName) && $foreignFieldName != $value->getTable()->getIdentifier()) { - $this->set($localFieldName, $value->rawGet($foreignFieldName), false); - } else { - $this->set($localFieldName, $value, false); - } - } else { - $value->set($foreignFieldName, $this, false); - } - } - } - - } else if ($rel instanceof IPF_ORM_Relation_Association) { - // join table relation found - if ( ! ($value instanceof IPF_ORM_Collection)) { - throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting many-to-many references."); - } - } - - $this->_references[$name] = $value; - } - - public function contains($fieldName) - { - if (isset($this->_data[$fieldName])) { - // this also returns true if the field is a IPF_ORM_Null. - // imho this is not correct behavior. - return true; - } - if (isset($this->_id[$fieldName])) { - return true; - } - if (isset($this->_values[$fieldName])) { - return true; - } - if (isset($this->_references[$fieldName]) && !IPF_ORM_Null::isNull($this->_references[$fieldName])) { - return true; - } - return false; - } - - public function __unset($name) - { - if (isset($this->_data[$name])) { - $this->_data[$name] = array(); - } else if (isset($this->_references[$name])) { - if ($this->_references[$name] instanceof IPF_ORM_Record) { - $this->_pendingDeletes[] = $this->$name; - $this->_references[$name] = IPF_ORM_Null::getInstance(); - } elseif ($this->_references[$name] instanceof IPF_ORM_Collection) { - $this->_pendingDeletes[] = $this->$name; - $this->_references[$name]->setData(array()); - } - } - } - - public function getPendingDeletes() - { - return $this->_pendingDeletes; - } - - public function save(IPF_ORM_Connection $conn = null) - { - if ($conn === null) { - $conn = $this->_table->getConnection(); - } - $conn->unitOfWork->saveGraph($this); - } - - public function trySave(IPF_ORM_Connection $conn = null) { - try { - $this->save($conn); - return true; - } catch (IPF_ORM_Exception_Validator $ignored) { - return false; - } - } - - public function replace(IPF_ORM_Connection $conn = null) - { - if ($conn === null) { - $conn = $this->_table->getConnection(); - } - - if ($this->exists()) { - return $this->save(); - } else { - $identifier = (array) $this->getTable()->getIdentifier(); - return $conn->replace($this->_table, $this->toArray(), $identifier); - } - } - - public function getModified() - { - $a = array(); - - foreach ($this->_modified as $k => $v) { - $a[$v] = $this->_data[$v]; - } - return $a; - } - - public function modifiedFields() - { - $a = array(); - - foreach ($this->_modified as $k => $v) { - $a[$v] = $this->_data[$v]; - } - return $a; - } - - public function getPrepared(array $array = array()) - { - $a = array(); - - if (empty($array)) { - $modifiedFields = $this->_modified; - } - - foreach ($modifiedFields as $field) { - $type = $this->_table->getTypeOf($field); - - if (IPF_ORM_Null::isNull($this->_data[$field])) { - $a[$field] = null; - continue; - } - - switch ($type) { - case 'array': - case 'object': - $a[$field] = serialize($this->_data[$field]); - break; - case 'gzip': - $a[$field] = gzcompress($this->_data[$field],5); - break; - case 'boolean': - $a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]); - break; - case 'enum': - $a[$field] = $this->_table->enumIndex($field, $this->_data[$field]); - break; - default: - if ($this->_data[$field] instanceof IPF_ORM_Record) { - $a[$field] = $this->_data[$field]->getIncremented(); - if ($a[$field] !== null) { - $this->_data[$field] = $a[$field]; - } - } else { - $a[$field] = $this->_data[$field]; - } - /** TODO: - if ($this->_data[$v] === null) { - throw new IPF_ORM_Record_Exception('Unexpected null value.'); - } - */ - } - } - $map = $this->_table->inheritanceMap; - foreach ($map as $k => $v) { - $k = $this->_table->getFieldName($k); - $old = $this->get($k, false); - - if ((string) $old !== (string) $v || $old === null) { - $a[$k] = $v; - $this->_data[$k] = $v; - } - } - - return $a; - } - - public function count() - { - return count($this->_data); - } - - public function columnCount() - { - return $this->count(); - } - - public function toArray($deep = true, $prefixKey = false) - { - if ($this->_state == self::STATE_LOCKED) { - return false; - } - - $stateBeforeLock = $this->_state; - $this->_state = self::STATE_LOCKED; - - $a = array(); - - foreach ($this as $column => $value) { - if (IPF_ORM_Null::isNull($value) || is_object($value)) { - $value = null; - } - - $a[$column] = $value; - } - - if ($this->_table->getIdentifierType() == IPF_ORM::IDENTIFIER_AUTOINC) { - $i = $this->_table->getIdentifier(); - $a[$i] = $this->getIncremented(); - } - - if ($deep) { - foreach ($this->_references as $key => $relation) { - if (!IPF_ORM_Null::isNull($relation)) { - $a[$key] = $relation->toArray($deep, $prefixKey); - } - } - } - - // [FIX] Prevent mapped IPF_ORM_Records from being displayed fully - foreach ($this->_values as $key => $value) { - if ($value instanceof IPF_ORM_Record) { - $a[$key] = $value->toArray($deep, $prefixKey); - } else { - $a[$key] = $value; - } - } - - $this->_state = $stateBeforeLock; - - return $a; - } - - public function merge($data, $deep = true) - { - if ($data instanceof $this) { - $array = $data->toArray($deep); - } else if (is_array($data)) { - $array = $data; - } - - return $this->fromArray($array, $deep); - } - - public function fromArray(array $array, $deep = true) - { - $refresh = false; - foreach ($array as $key => $value) { - if ($key == '_identifier') { - $refresh = true; - $this->assignIdentifier((array) $value); - continue; - } - - if ($deep && $this->getTable()->hasRelation($key)) { - $this->$key->fromArray($value, $deep); - } else if ($this->getTable()->hasField($key)) { - $this->set($key, $value); - } - } - - if ($refresh) { - $this->refresh(); - } - } - - protected function _synchronizeWithArrayForRelation($key, $value) - { - $this->get($key)->synchronizeWithArray($value); - } - - public function synchronizeWithArray(array $array, $deep = true) - { - $refresh = false; - foreach ($array as $key => $value) { - if ($key == '_identifier') { - $refresh = true; - $this->assignIdentifier((array) $value); - continue; - } - if ($deep && $this->getTable()->hasRelation($key)) { - $this->_synchronizeWithArrayForRelation($key, $value); - } else if ($this->getTable()->hasField($key)) { - $this->set($key, $value); - } - } - // eliminate relationships missing in the $array - foreach ($this->_references as $name => $obj) { - if ( ! isset($array[$name])) { - unset($this->$name); - } - } - if ($refresh) { - $this->refresh(); - } - } - - public function exists() - { - return ($this->_state !== IPF_ORM_Record::STATE_TCLEAN && - $this->_state !== IPF_ORM_Record::STATE_TDIRTY); - } - - public function isModified() - { - return ($this->_state === IPF_ORM_Record::STATE_DIRTY || - $this->_state === IPF_ORM_Record::STATE_TDIRTY); - } - - public function hasRelation($fieldName) - { - if (isset($this->_data[$fieldName]) || isset($this->_id[$fieldName])) { - return true; - } - return $this->_table->hasRelation($fieldName); - } - - public function getIterator() - { - return new IPF_ORM_Record_Iterator($this); - } - - public function delete(IPF_ORM_Connection $conn = null) - { - if ($conn == null) { - $conn = $this->_table->getConnection(); - } - return $conn->unitOfWork->delete($this); - } - - public function copy($deep = false) - { - $data = $this->_data; - - if ($this->_table->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC) { - $id = $this->_table->getIdentifier(); - - unset($data[$id]); - } - - $ret = $this->_table->create($data); - $modified = array(); - - foreach ($data as $key => $val) { - if (!IPF_ORM_Null::isNull($val)) { - $ret->_modified[] = $key; - } - } - - if ($deep) { - foreach ($this->_references as $key => $value) { - if ($value instanceof IPF_ORM_Collection) { - foreach ($value as $record) { - $ret->{$key}[] = $record->copy($deep); - } - } else if($value instanceof IPF_ORM_Record) { - $ret->set($key, $value->copy($deep)); - } - } - } - - return $ret; - } - - public function assignIdentifier($id = false) - { - if ($id === false) { - $this->_id = array(); - $this->_data = $this->cleanData($this->_data); - $this->_state = IPF_ORM_Record::STATE_TCLEAN; - $this->_modified = array(); - } elseif ($id === true) { - $this->prepareIdentifiers(true); - $this->_state = IPF_ORM_Record::STATE_CLEAN; - $this->_modified = array(); - } else { - if (is_array($id)) { - foreach ($id as $fieldName => $value) { - $this->_id[$fieldName] = $value; - $this->_data[$fieldName] = $value; - } - } else { - $name = $this->_table->getIdentifier(); - $this->_id[$name] = $id; - $this->_data[$name] = $id; - } - $this->_state = IPF_ORM_Record::STATE_CLEAN; - $this->_modified = array(); - } - } - - public function identifier() - { - return $this->_id; - } - - final public function getIncremented() - { - $id = current($this->_id); - if ($id === false) { - return null; - } - - return $id; - } - - public function pk($sep='_') - { - $pk = ''; - foreach($this->_id as $val) { - if ($pk!='') - $pk .= $sep; - $pk .= $val; - } - return $pk; - } - - public function getLast() - { - return $this; - } - - public function hasReference($name) - { - return isset($this->_references[$name]); - } - - public function reference($name) - { - if (isset($this->_references[$name])) { - return $this->_references[$name]; - } - } - - public function obtainReference($name) - { - if (isset($this->_references[$name])) { - return $this->_references[$name]; - } - throw new IPF_ORM_Exception("Unknown reference $name"); - } - - public function getReferences() - { - return $this->_references; - } - - final public function setRelated($alias, IPF_ORM_Access $coll) - { - $this->_references[$alias] = $coll; - } - - public function loadReference($name) - { - $rel = $this->_table->getRelation($name); - $this->_references[$name] = $rel->fetchRelatedFor($this); - } - - public function call($callback, $column) - { - $args = func_get_args(); - array_shift($args); - - if (isset($args[0])) { - $fieldName = $args[0]; - $args[0] = $this->get($fieldName); - - $newvalue = call_user_func_array($callback, $args); - - $this->_data[$fieldName] = $newvalue; - } - return $this; - } - - public function unlink($alias, $ids = array()) - { - $ids = (array) $ids; - - $q = new IPF_ORM_Query(); - - $rel = $this->getTable()->getRelation($alias); - - if ($rel instanceof IPF_ORM_Relation_Association) { - $q->delete() - ->from($rel->getAssociationTable()->getComponentName()) - ->where($rel->getLocal() . ' = ?', array_values($this->identifier())); - - if (count($ids) > 0) { - $q->whereIn($rel->getForeign(), $ids); - } - - $q->execute(); - - } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) { - $q->update($rel->getTable()->getComponentName()) - ->set($rel->getForeign(), '?', array(null)) - ->addWhere($rel->getForeign() . ' = ?', array_values($this->identifier())); - - if (count($ids) > 0) { - $q->whereIn($rel->getTable()->getIdentifier(), $ids); - } - - $q->execute(); - } - if (isset($this->_references[$alias])) { - foreach ($this->_references[$alias] as $k => $record) { - if (in_array(current($record->identifier()), $ids)) { - $this->_references[$alias]->remove($k); - } - } - $this->_references[$alias]->takeSnapshot(); - } - return $this; - } - - public function link($alias, $ids) - { - $ids = (array) $ids; - - if ( ! count($ids)) { - return $this; - } - - $identifier = array_values($this->identifier()); - $identifier = array_shift($identifier); - - $rel = $this->getTable()->getRelation($alias); - - if ($rel instanceof IPF_ORM_Relation_Association) { - - $modelClassName = $rel->getAssociationTable()->getComponentName(); - $localFieldName = $rel->getLocalFieldName(); - $localFieldDef = $rel->getAssociationTable()->getColumnDefinition($localFieldName); - if ($localFieldDef['type'] == 'integer') { - $identifier = (integer) $identifier; - } - $foreignFieldName = $rel->getForeignFieldName(); - $foreignFieldDef = $rel->getAssociationTable()->getColumnDefinition($foreignFieldName); - if ($foreignFieldDef['type'] == 'integer') { - for ($i = 0; $i < count($ids); $i++) { - $ids[$i] = (integer) $ids[$i]; - } - } - - foreach ($ids as $id) { - $record = new $modelClassName; - $record[$localFieldName] = $identifier; - $record[$foreignFieldName] = $id; - $record->save(); - } - - } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) { - - $q = new IPF_ORM_Query(); - - $q->update($rel->getTable()->getComponentName()) - ->set($rel->getForeign(), '?', array_values($this->identifier())); - - if (count($ids) > 0) { - $q->whereIn($rel->getTable()->getIdentifier(), $ids); - } - - $q->execute(); - - } else if ($rel instanceof IPF_ORM_Relation_LocalKey) { - - $q = new IPF_ORM_Query(); - - $q->update($this->getTable()->getComponentName()) - ->set($rel->getLocalFieldName(), '?', $ids); - - if (count($ids) > 0) { - $q->whereIn($rel->getTable()->getIdentifier(), array_values($this->identifier())); - } - - $q->execute(); - - } - - return $this; - } - - public function __call($method, $args) - { - if (($template = $this->_table->getMethodOwner($method)) !== false) { - $template->setInvoker($this); - return call_user_func_array(array($template, $method), $args); - } - - foreach ($this->_table->getTemplates() as $template) { - if (method_exists($template, $method)) { - $template->setInvoker($this); - $this->_table->setMethodOwner($method, $template); - - return call_user_func_array(array($template, $method), $args); - } - } - - throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method)); - } - - public function free($deep = false) - { - if ($this->_state != self::STATE_LOCKED) { - $this->_state = self::STATE_LOCKED; - - $this->_table->getRepository()->evict($this->_oid); - $this->_table->removeRecord($this); - $this->_data = array(); - $this->_id = array(); - - if ($deep) { - foreach ($this->_references as $name => $reference) { - if (!IPF_ORM_Null::isNull($reference)) { - $reference->free($deep); - } - } - } - - $this->_references = array(); - } - } - - public function toString() - { - return IPF_ORM::dump(get_object_vars($this)); - } - - public function __toString() - { - return sprintf('<%s #%d>', get_class($this), $this->_oid); - } - - public function SetFromFormData($cleaned_values) - { - $names = $this->_table->getFieldNames(); - foreach ($cleaned_values as $key=>$val) { - $validators = $this->getTable()->getFieldValidators($key); - if ( - array_key_exists('image',$validators) || - array_key_exists('file',$validators) || - array_key_exists('email',$validators) - ){ - if (($val!==null) && ($val=='')) - continue; - } - if (array_search($key,$names)){ - $this->$key = $val; - } - } - } - - public function SetCustom($name, $val) - { - $this->_custom[$name] = $val; - } - - public function GetCustom($name) - { - if (isset($this->_custom[$name])) - return $this->_custom[$name]; - return null; - } -} - diff --git a/ipf/legacy_orm/orm/record/abstract.php b/ipf/legacy_orm/orm/record/abstract.php deleted file mode 100644 index 07aab0d..0000000 --- a/ipf/legacy_orm/orm/record/abstract.php +++ /dev/null @@ -1,60 +0,0 @@ -_table; - } - - public function setInheritanceMap($map) - { - $this->_table->setOption('inheritanceMap', $map); - } - - public function attribute($attr, $value) - { - if ($value == null) { - if (is_array($attr)) { - foreach ($attr as $k => $v) { - $this->_table->setAttribute($k, $v); - } - } else { - return $this->_table->getAttribute($attr); - } - } else { - $this->_table->setAttribute($attr, $value); - } - } - - public function check($constraint, $name = null) - { - if (is_array($constraint)) { - foreach ($constraint as $name => $def) { - $this->_table->addCheckConstraint($def, $name); - } - } else { - $this->_table->addCheckConstraint($constraint, $name); - } - return $this; - } - - public function __debugInfo() - { - $r = array(); - foreach ($this->getTable()->getColumnNames() as $column) - $r[$column] = $this->get($column); - return $r; - } -} - diff --git a/ipf/legacy_orm/orm/record/iterator.php b/ipf/legacy_orm/orm/record/iterator.php deleted file mode 100644 index 505a990..0000000 --- a/ipf/legacy_orm/orm/record/iterator.php +++ /dev/null @@ -1,24 +0,0 @@ -record = $record; - parent::__construct($record->getData()); - } - - public function current() - { - $value = parent::current(); - - if (IPF_ORM_Null::isNull($value)) { - return null; - } else { - return $value; - } - } -} - diff --git a/ipf/legacy_orm/orm/relation.php b/ipf/legacy_orm/orm/relation.php deleted file mode 100644 index 3d0c550..0000000 --- a/ipf/legacy_orm/orm/relation.php +++ /dev/null @@ -1,175 +0,0 @@ - true, - 'foreign' => true, - 'local' => true, - 'class' => true, - 'type' => true, - 'table' => true, - 'localTable' => true, - 'name' => null, - 'refTable' => null, - 'onDelete' => null, - 'onUpdate' => null, - 'deferred' => null, - 'deferrable' => null, - 'constraint' => null, - 'equal' => false, - 'cascade' => array(), // application-level cascades - 'owningSide' => false, // whether this is the owning side - 'exclude' => false, - ); - - public function __construct(array $definition) - { - $def = array(); - foreach ($this->definition as $key => $val) { - if ( ! isset($definition[$key]) && $val) { - throw new IPF_ORM_Exception($key . ' is required!'); - } - if (isset($definition[$key])) { - $def[$key] = $definition[$key]; - } else { - $def[$key] = $this->definition[$key]; - } - } - $this->definition = $def; - } - - public function hasConstraint() - { - return $this->definition['constraint'] || - $this->definition['onUpdate'] || - $this->definition['onDelete']; - } - - public function isDeferred() - { - return $this->definition['deferred']; - } - - public function isDeferrable() - { - return $this->definition['deferrable']; - } - public function isEqual() - { - return $this->definition['equal']; - } - - public function offsetExists($offset) - { - return isset($this->definition[$offset]); - } - - public function offsetGet($offset) - { - if (isset($this->definition[$offset])) { - return $this->definition[$offset]; - } - - return null; - } - - public function offsetSet($offset, $value) - { - if (isset($this->definition[$offset])) { - $this->definition[$offset] = $value; - } - } - - public function offsetUnset($offset) - { - $this->definition[$offset] = false; - } - - public function toArray() - { - return $this->definition; - } - - final public function getAlias() - { - return $this->definition['alias']; - } - - final public function getType() - { - return $this->definition['type']; - } - - public function isCascadeDelete() - { - return in_array('delete', $this->definition['cascade']); - } - - final public function getTable() - { - return IPF_ORM_Manager::connection()->getTable($this->definition['class']); - } - - final public function getClass() - { - return $this->definition['class']; - } - - final public function getLocal() - { - return $this->definition['local']; - } - - final public function getLocalFieldName() - { - return $this->definition['localTable']->getFieldName($this->definition['local']); - } - - final public function getForeign() - { - return $this->definition['foreign']; - } - - final public function getForeignFieldName() - { - return $this->definition['table']->getFieldName($this->definition['foreign']); - } - - final public function isComposite() - { - return ($this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE || - $this->definition['type'] == IPF_ORM_Relation::MANY_COMPOSITE); - } - - final public function isOneToOne() - { - return ($this->definition['type'] == IPF_ORM_Relation::ONE_AGGREGATE || - $this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE); - } - - abstract public function getRelationDql($count, $context); - - abstract public function fetchRelatedFor(IPF_ORM_Record $record); - - public function __toString() - { - $r[] = "
";
-        foreach ($this->definition as $k => $v) {
-            if (is_object($v)) {
-                $v = 'Object(' . get_class($v) . ')';
-            }
-            $r[] = $k . ' : ' . $v;
-        }
-        $r[] = "
"; - return implode("\n", $r); - } -} - diff --git a/ipf/legacy_orm/orm/relation/association.php b/ipf/legacy_orm/orm/relation/association.php deleted file mode 100644 index 29fc2b2..0000000 --- a/ipf/legacy_orm/orm/relation/association.php +++ /dev/null @@ -1,45 +0,0 @@ -definition['refTable']; - } - public function getAssociationTable() - { - return $this->definition['refTable']; - } - - public function getRelationDql($count, $context) - { - $component = $this->definition['refTable']->getComponentName(); - $thisTable = $this->getTable()->getComponentName(); - - $sub = substr(str_repeat('?, ', $count), 0, -2); - switch ($context) { - case "record": - $dql = 'FROM ' . $thisTable . '.' . $component . - ' WHERE ' . $thisTable . '.' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')'; - break; - case "collection": - $dql = 'FROM ' . $component . '.' . $thisTable . - ' WHERE ' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')'; - break; - } - return $dql; - } - - public function fetchRelatedFor(IPF_ORM_Record $record) - { - $id = $record->pk(); - if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { - $coll = new IPF_ORM_Collection($this->getTable()); - } else { - $coll = $this->getTable()->getConnection()->query($this->getRelationDql(1, 'record'), array($id)); - } - $coll->setReference($record, $this); - return $coll; - } -} - diff --git a/ipf/legacy_orm/orm/relation/foreignkey.php b/ipf/legacy_orm/orm/relation/foreignkey.php deleted file mode 100644 index 5e5a91f..0000000 --- a/ipf/legacy_orm/orm/relation/foreignkey.php +++ /dev/null @@ -1,72 +0,0 @@ -getTable(); - foreach ((array) $this->definition['local'] as $local) { - $value = $record->get($localTable->getFieldName($local)); - if (isset($value)) { - $id[] = $value; - } - } - if ($this->isOneToOne()) { - if ( ! $record->exists() || empty($id) || - ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { - - $related = $this->getTable()->create(); - } else { - $dql = 'FROM ' . $this->getTable()->getComponentName() - . ' WHERE ' . $this->getCondition(); - - $coll = $this->getTable()->getConnection()->query($dql, $id); - $related = $coll[0]; - } - - $related->set($related->getTable()->getFieldName($this->definition['foreign']), - $record, false); - } else { - - if ( ! $record->exists() || empty($id) || - ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { - - $related = new IPF_ORM_Collection($this->getTable()); - } else { - $query = $this->getRelationDql(1, ''); - $related = $this->getTable()->getConnection()->query($query, $id); - } - $related->setReference($record, $this); - } - return $related; - } - - public function getRelationDql($count, $context) - { - $table = $this->getTable(); - $component = $table->getComponentName(); - - $dql = 'FROM ' . $component - . ' WHERE ' . $component . '.' . $this->definition['foreign'] - . ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')'; - - $ordering = $table->getOrdering(); - if ($ordering) - $dql .= ' ORDER BY ' . implode(', ', $ordering); - - return $dql; - } - - public function getCondition($alias = null) - { - if ( ! $alias) { - $alias = $this->getTable()->getComponentName(); - } - $conditions = array(); - foreach ((array) $this->definition['foreign'] as $foreign) { - $conditions[] = $alias . '.' . $foreign . ' = ?'; - } - return implode(' AND ', $conditions); - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/relation/localkey.php b/ipf/legacy_orm/orm/relation/localkey.php deleted file mode 100644 index edd225f..0000000 --- a/ipf/legacy_orm/orm/relation/localkey.php +++ /dev/null @@ -1,47 +0,0 @@ -getTable()->getFieldName($this->definition['local']); - $id = $record->get($localFieldName); - - if (is_null($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { - $related = $this->getTable()->create(); - } else { - $dql = 'FROM ' . $this->getTable()->getComponentName() - . ' WHERE ' . $this->getCondition(); - - $related = $this->getTable() - ->getConnection() - ->query($dql, array($id)) - ->getFirst(); - - if ( ! $related || empty($related)) { - $related = $this->getTable()->create(); - } - } - - $record->set($localFieldName, $related, false); - - return $related; - } - - public function getRelationDql($count, $context) - { - $component = $this->getTable()->getComponentName(); - return 'FROM ' . $component . - ' WHERE ' . $component . '.' . $this->definition['foreign'] . - ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')'; - } - - public function getCondition($alias = null) - { - if ( ! $alias) { - $alias = $this->getTable()->getComponentName(); - } - return $alias . '.' . $this->definition['foreign'] . ' = ?'; - } -} - diff --git a/ipf/legacy_orm/orm/relation/nest.php b/ipf/legacy_orm/orm/relation/nest.php deleted file mode 100644 index 9e27245..0000000 --- a/ipf/legacy_orm/orm/relation/nest.php +++ /dev/null @@ -1,85 +0,0 @@ -definition['table']->getIdentifierColumnNames(); - $identifier = array_pop($identifierColumnNames); - $sub = 'SELECT '.$this->definition['foreign'] - . ' FROM '.$this->definition['refTable']->getTableName() - . ' WHERE '.$this->definition['local'] - . ' = ?'; - - $sub2 = 'SELECT '.$this->definition['local'] - . ' FROM '.$this->definition['refTable']->getTableName() - . ' WHERE '.$this->definition['foreign'] - . ' = ?'; - - $dql = 'FROM ' . $this->definition['table']->getComponentName() - . '.' . $this->definition['refTable']->getComponentName() - . ' WHERE ' . $this->definition['table']->getComponentName() - . '.' . $identifier - . ' IN (' . $sub . ')' - . ' || ' . $this->definition['table']->getComponentName() - . '.' . $identifier - . ' IN (' . $sub2 . ')'; - break; - case 'collection': - $sub = substr(str_repeat('?, ', $count),0,-2); - $dql = 'FROM '.$this->definition['refTable']->getComponentName() - . '.' . $this->definition['table']->getComponentName() - . ' WHERE '.$this->definition['refTable']->getComponentName() - . '.' . $this->definition['local'] . ' IN (' . $sub . ')'; - }; - - return $dql; - } - - public function fetchRelatedFor(IPF_ORM_Record $record) - { - $id = $record->getIncremented(); - - - if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { - return new IPF_ORM_Collection($this->getTable()); - } else { - $q = new IPF_ORM_RawSql($this->getTable()->getConnection()); - - $assocTable = $this->getAssociationFactory()->getTableName(); - $tableName = $record->getTable()->getTableName(); - $identifierColumnNames = $record->getTable()->getIdentifierColumnNames(); - $identifier = array_pop($identifierColumnNames); - - $sub = 'SELECT ' . $this->getForeign() - . ' FROM ' . $assocTable - . ' WHERE ' . $this->getLocal() - . ' = ?'; - - $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub . ')'; - $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getForeign(); - - if ($this->definition['equal']) { - $sub2 = 'SELECT ' . $this->getLocal() - . ' FROM ' . $assocTable - . ' WHERE ' . $this->getForeign() - . ' = ?'; - - $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub2 . ')'; - $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getLocal(); - } - $q->select('{'.$tableName.'.*}') - ->addSelect('{'.$assocTable.'.*}') - ->from($tableName . ' INNER JOIN ' . $assocTable . ' ON ' . implode(' OR ', $joinCondition)) - ->where(implode(' OR ', $condition)); - $q->addComponent($tableName, $record->getTable()->getComponentName()); - $q->addComponent($assocTable, $record->getTable()->getComponentName(). '.' . $this->getAssociationFactory()->getComponentName()); - - $params = ($this->definition['equal']) ? array($id, $id) : array($id); - - return $q->execute($params); - } - } -} diff --git a/ipf/legacy_orm/orm/relation/parser.php b/ipf/legacy_orm/orm/relation/parser.php deleted file mode 100644 index ce1fe89..0000000 --- a/ipf/legacy_orm/orm/relation/parser.php +++ /dev/null @@ -1,362 +0,0 @@ -_table = $table; - } - - public function getTable() - { - return $this->_table; - } - - public function getPendingRelation($name) - { - if ( ! isset($this->_pending[$name])) { - throw new IPF_ORM_Exception('Unknown pending relation ' . $name); - } - - return $this->_pending[$name]; - } - - public function getPendingRelations() - { - return $this->_pending; - } - - public function unsetPendingRelations($name) - { - unset($this->_pending[$name]); - } - - public function hasRelation($name) - { - return isset($this->_pending[$name]) || isset($this->_relations[$name]); - } - - public function bind($name, $alias, $type, $options=array()) - { - if (!$alias) - $alias = $name; - - unset($this->relations[$alias]); - - $options['class'] = $name; - $options['alias'] = $alias; - $options['type'] = $type; - - $this->_pending[$alias] = $options; - } - - private function createRelation($alias) - { - if (isset($this->_relations[$alias])) - return; - - if (!isset($this->_pending[$alias])) - throw new IPF_ORM_Exception('Unknown relation alias "' . $alias . '".'); - - $def = $this->_pending[$alias]; - $identifierColumnNames = $this->_table->getIdentifierColumnNames(); - $idColumnName = array_pop($identifierColumnNames); - - // check if reference class name exists - // if it does we are dealing with association relation - if (isset($def['refClass'])) { - $def = $this->completeAssocDefinition($def); - $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName())); - - if ( ! isset($this->_pending[$def['refClass']]) && - ! isset($this->_relations[$def['refClass']])) { - - $parser = $def['refTable']->getRelationParser(); - if ( ! $parser->hasRelation($this->_table->getComponentName())) { - $parser->bind($this->_table->getComponentName(), null, IPF_ORM_Relation::ONE, array( - 'local' => $def['local'], - 'foreign' => $idColumnName, - 'localKey' => true, - )); - } - - if (!$this->hasRelation($def['refClass'])) { - $this->bind($def['refClass'], null, IPF_ORM_Relation::MANY, array( - 'foreign' => $def['local'], - 'local' => $idColumnName, - )); - } - } - if (in_array($def['class'], $localClasses)) { - $rel = new IPF_ORM_Relation_Nest($def); - } else { - $rel = new IPF_ORM_Relation_Association($def); - } - } else { - // simple foreign key relation - $def = $this->completeDefinition($def); - - if (isset($def['localKey'])) { - $rel = new IPF_ORM_Relation_LocalKey($def); - - // Automatically index foreign keys which are not primary - $foreign = (array) $def['foreign']; - foreach ($foreign as $fk) { - if ( ! $rel->getTable()->isIdentifier($fk)) { - $rel->getTable()->addIndex($fk, array('fields' => array($fk))); - } - } - } else { - $rel = new IPF_ORM_Relation_ForeignKey($def); - } - } - - if (isset($rel)) { - // unset pending relation - unset($this->_pending[$alias]); - - $this->_relations[$alias] = $rel; - } - } - - public function getRelation($alias) - { - $this->getRelations(); - return $this->_relations[$alias]; - } - - public function getRelations() - { - foreach ($this->_pending as $k => $v) { - $this->createRelation($k); - } - - return $this->_relations; - } - - public function completeAssocDefinition($def) - { - $conn = $this->_table->getConnection(); - - $def['table'] = IPF_ORM::getTable($def['class']); - $def['localTable'] = $this->_table; - $def['class'] = $def['table']->getComponentName(); - $def['refTable'] = IPF_ORM::getTable($def['refClass']); - - $id = $def['refTable']->getIdentifierColumnNames(); - - if (count($id) > 1) { - if ( ! isset($def['foreign'])) { - // foreign key not set - // try to guess the foreign key - - $def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0]; - } - if ( ! isset($def['local'])) { - // foreign key not set - // try to guess the foreign key - - $def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0]; - } - } else { - - if ( ! isset($def['foreign'])) { - // foreign key not set - // try to guess the foreign key - - $columns = $this->getIdentifiers($def['table']); - - $def['foreign'] = $columns; - } - if ( ! isset($def['local'])) { - // local key not set - // try to guess the local key - $columns = $this->getIdentifiers($this->_table); - - $def['local'] = $columns; - } - } - return $def; - } - - public function getIdentifiers(IPF_ORM_Table $table) - { - $componentNameToLower = strtolower($table->getComponentName()); - if (is_array($table->getIdentifier())) { - $columns = array(); - foreach ((array) $table->getIdentifierColumnNames() as $identColName) { - $columns[] = $componentNameToLower . '_' . $identColName; - } - } else { - $columns = $componentNameToLower . '_' . $table->getColumnName( - $table->getIdentifier()); - } - - return $columns; - } - - public function guessColumns(array $classes, IPF_ORM_Table $foreignTable) - { - $conn = $this->_table->getConnection(); - - foreach ($classes as $class) { - try { - $table = $conn->getTable($class); - } catch (IPF_ORM_Exception $e) { - continue; - } - $columns = $this->getIdentifiers($table); - $found = true; - - foreach ((array) $columns as $column) { - if ( ! $foreignTable->hasColumn($column)) { - $found = false; - break; - } - } - if ($found) { - break; - } - } - - if ( ! $found) { - throw new IPF_ORM_Exception("Couldn't find columns."); - } - - return $columns; - } - - public function completeDefinition($def) - { - $conn = $this->_table->getConnection(); - $def['table'] = IPF_ORM::getTable($def['class']); - $def['localTable'] = $this->_table; - $def['class'] = $def['table']->getComponentName(); - - $foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class'])); - $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName())); - - $localIdentifierColumnNames = $this->_table->getIdentifierColumnNames(); - $localIdentifierCount = count($localIdentifierColumnNames); - $localIdColumnName = array_pop($localIdentifierColumnNames); - $foreignIdentifierColumnNames = $def['table']->getIdentifierColumnNames(); - $foreignIdColumnName = array_pop($foreignIdentifierColumnNames); - - if (isset($def['local'])) { - if ( ! isset($def['foreign'])) { - // local key is set, but foreign key is not - // try to guess the foreign key - - if ($def['local'] === $localIdColumnName) { - $def['foreign'] = $this->guessColumns($localClasses, $def['table']); - } else { - // the foreign field is likely to be the - // identifier of the foreign class - $def['foreign'] = $foreignIdColumnName; - $def['localKey'] = true; - } - } else { - if ($localIdentifierCount == 1) { - if ($def['local'] == $localIdColumnName && isset($def['owningSide']) - && $def['owningSide'] === true) { - $def['localKey'] = true; - } else if (($def['local'] !== $localIdColumnName && $def['type'] == IPF_ORM_Relation::ONE)) { - $def['localKey'] = true; - } - } else if ($localIdentifierCount > 1) { - // It's a composite key and since 'foreign' can not point to a composite - // key currently, we know that 'local' must be the foreign key. - $def['localKey'] = true; - } - } - } else { - if (isset($def['foreign'])) { - // local key not set, but foreign key is set - // try to guess the local key - if ($def['foreign'] === $foreignIdColumnName) { - $def['localKey'] = true; - try { - $def['local'] = $this->guessColumns($foreignClasses, $this->_table); - } catch (IPF_ORM_Exception $e) { - $def['local'] = $localIdColumnName; - } - } else { - $def['local'] = $localIdColumnName; - } - } else { - // neither local or foreign key is being set - // try to guess both keys - - $conn = $this->_table->getConnection(); - - // the following loops are needed for covering inheritance - foreach ($localClasses as $class) { - $table = $conn->getTable($class); - $identifierColumnNames = $table->getIdentifierColumnNames(); - $idColumnName = array_pop($identifierColumnNames); - $column = strtolower($table->getComponentName()) - . '_' . $idColumnName; - - foreach ($foreignClasses as $class2) { - $table2 = $conn->getTable($class2); - if ($table2->hasColumn($column)) { - $def['foreign'] = $column; - $def['local'] = $idColumnName; - return $def; - } - } - } - - foreach ($foreignClasses as $class) { - $table = $conn->getTable($class); - $identifierColumnNames = $table->getIdentifierColumnNames(); - $idColumnName = array_pop($identifierColumnNames); - $column = strtolower($table->getComponentName()) - . '_' . $idColumnName; - - foreach ($localClasses as $class2) { - $table2 = $conn->getTable($class2); - if ($table2->hasColumn($column)) { - $def['foreign'] = $idColumnName; - $def['local'] = $column; - $def['localKey'] = true; - return $def; - } - } - } - - // auto-add columns and auto-build relation - $columns = array(); - foreach ((array) $this->_table->getIdentifierColumnNames() as $id) { - // ?? should this not be $this->_table->getComponentName() ?? - $column = strtolower($table->getComponentName()) - . '_' . $id; - - $col = $this->_table->getColumnDefinition($id); - $type = $col['type']; - $length = $col['length']; - - unset($col['type']); - unset($col['length']); - unset($col['autoincrement']); - unset($col['primary']); - - $def['table']->setColumn($column, $type, $length, $col); - - $columns[] = $column; - } - if (count($columns) > 1) { - $def['foreign'] = $columns; - } else { - $def['foreign'] = $columns[0]; - } - $def['local'] = $localIdColumnName; - } - } - return $def; - } -} diff --git a/ipf/legacy_orm/orm/table.php b/ipf/legacy_orm/orm/table.php deleted file mode 100644 index 6208103..0000000 --- a/ipf/legacy_orm/orm/table.php +++ /dev/null @@ -1,1033 +0,0 @@ - null, - 'tableName' => null, - 'inheritanceMap' => array(), - 'enumMap' => array(), - 'type' => null, - 'charset' => null, - 'collation' => null, - 'indexes' => array(), - 'parents' => array(), - 'queryParts' => array(), - 'versioning' => null, - 'subclasses' => array(), - ); - - protected $_ordering = null; - - protected $_parser; - - protected $_templates = array(); - protected $_invokedMethods = array(); - - public $listeners = array(); - - public function __construct($name, IPF_ORM_Connection $conn) - { - if (empty($name) || !class_exists($name)) - throw new IPF_ORM_Exception("Couldn't find class " . $name); - - $this->_conn = $conn; - $this->setParent($this->_conn); - - $this->_options['name'] = $name; - $this->_parser = new IPF_ORM_Relation_Parser($this); - - $this->initParents($name); - - // create database table - $name::setTableDefinition($this); - - if (!isset($this->_options['tableName'])) { - $this->setTableName(IPF_ORM_Inflector::tableize($class->getName())); - } - - $this->initIdentifier(); - - $name::setUp($this); - - $this->_repository = new IPF_ORM_Table_Repository($this); - } - - private function initParents($name) - { - $names = array(); - - $class = $name; - do { - if ($class === 'IPF_ORM_Record') - break; - - $name = $class; - $names[] = $name; - } while ($class = get_parent_class($class)); - - if ($class === false) - throw new IPF_ORM_Exception('Class "' . $name . '" must be a child class of IPF_ORM_Record'); - - // reverse names - $names = array_reverse($names); - // save parents - array_pop($names); - $this->_options['parents'] = $names; - } - - public function initIdentifier() - { - switch (count($this->_identifier)) { - case 0: - $definition = array('type' => 'integer', - 'length' => 20, - 'autoincrement' => true, - 'primary' => true); - $this->setColumn('id', $definition['type'], $definition['length'], $definition, true); - $this->_identifier = 'id'; - $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC; - break; - case 1: - foreach ($this->_identifier as $pk) { - $e = $this->getDefinitionOf($pk); - - $found = false; - - foreach ($e as $option => $value) { - if ($found) { - break; - } - - $e2 = explode(':', $option); - - switch (strtolower($e2[0])) { - case 'autoincrement': - case 'autoinc': - if ($value !== false) { - $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC; - $found = true; - } - break; - } - } - if ( ! isset($this->_identifierType)) { - $this->_identifierType = IPF_ORM::IDENTIFIER_NATURAL; - } - } - - $this->_identifier = $pk; - - break; - default: - $this->_identifierType = IPF_ORM::IDENTIFIER_COMPOSITE; - } - } - - public function isIdentifier($fieldName) - { - return ($fieldName === $this->getIdentifier() || - in_array($fieldName, (array) $this->getIdentifier())); - } - - public function isIdentifierAutoincrement() - { - return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC; - } - - public function isIdentifierComposite() - { - return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_COMPOSITE; - } - - public function getMethodOwner($method) - { - return (isset($this->_invokedMethods[$method])) ? - $this->_invokedMethods[$method] : false; - } - - public function setMethodOwner($method, $class) - { - $this->_invokedMethods[$method] = $class; - } - - public function getTemplates() - { - return $this->_templates; - } - - public function getRelationParser() - { - return $this->_parser; - } - - public function __get($option) - { - if (isset($this->_options[$option])) { - return $this->_options[$option]; - } - return null; - } - - public function __isset($option) - { - return isset($this->_options[$option]); - } - - public function getOptions() - { - return $this->_options; - } - - public function setOptions($options) - { - foreach ($options as $key => $value) { - $this->setOption($key, $value); - } - } - - public function addCheckConstraint($definition, $name) - { - if (is_string($name)) { - $this->_options['checks'][$name] = $definition; - } else { - $this->_options['checks'][] = $definition; - } - - return $this; - } - - public function addIndex($index, array $definition) - { - $this->_options['indexes'][$index] = $definition; - } - - public function getIndex($index) - { - if (isset($this->_options['indexes'][$index])) { - return $this->_options['indexes'][$index]; - } - - return false; - } - - public function bind($class, $alias, $type, array $options) - { - $this->_parser->bind($class, $alias, $type, $options); - } - - public function ownsOne($class, $alias, $options=array()) - { - $this->bind($class, $alias, IPF_ORM_Relation::ONE_COMPOSITE, $options); - } - - public function ownsMany($class, $alias, $options=array()) - { - $this->bind($class, $alias, IPF_ORM_Relation::MANY_COMPOSITE, $options); - } - - public function hasOne($class, $alias, $options=array()) - { - $this->bind($class, $alias, IPF_ORM_Relation::ONE_AGGREGATE, $options); - } - - public function hasMany($class, $alias, $options=array()) - { - $this->bind($class, $alias, IPF_ORM_Relation::MANY_AGGREGATE, $options); - } - - public function hasRelation($alias) - { - return $this->_parser->hasRelation($alias); - } - - public function getRelation($alias) - { - return $this->_parser->getRelation($alias); - } - - public function getRelations() - { - return $this->_parser->getRelations(); - } - - public function createQuery($alias = '') - { - if ( ! empty($alias)) { - $alias = ' ' . trim($alias); - } - return IPF_ORM_Query::create($this->_conn)->from($this->getComponentName() . $alias); - } - - public function getRepository() - { - return $this->_repository; - } - - public function setOption($name, $value) - { - switch ($name) { - case 'name': - case 'tableName': - break; - case 'enumMap': - case 'inheritanceMap': - case 'index': - if (!is_array($value)) - throw new IPF_ORM_Exception($name . ' should be an array.'); - break; - } - $this->_options[$name] = $value; - } - - public function getOption($name, $default=null) - { - if (isset($this->_options[$name])) { - return $this->_options[$name]; - } - return $default; - } - - public function getColumnName($fieldName) - { - // FIX ME: This is being used in places where an array is passed, but it should not be an array - // For example in places where IPF_ORM should support composite foreign/primary keys - $fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName; - - if (isset($this->_columnNames[$fieldName])) { - return $this->_columnNames[$fieldName]; - } - - return strtolower($fieldName); - } - - public function getColumnDefinition($columnName, $default=false) - { - if (isset($this->_columns[$columnName])) { - return $this->_columns[$columnName]; - } - return false; - } - - public function getFieldName($columnName) - { - if (isset($this->_fieldNames[$columnName])) { - return $this->_fieldNames[$columnName]; - } - return $columnName; - } - public function setColumns(array $definitions) - { - foreach ($definitions as $name => $options) { - $this->setColumn($name, $options['type'], $options['length'], $options); - } - } - - public function setColumn($name, $type, $length = null, $options = array(), $prepend = false) - { - if (is_string($options)) { - $options = explode('|', $options); - } - - foreach ($options as $k => $option) { - if (is_numeric($k)) { - if (!empty($option)) { - $options[$option] = true; - } - unset($options[$k]); - } - } - - // extract column name & field name - if (stripos($name, ' as ')) - { - if (strpos($name, ' as')) { - $parts = explode(' as ', $name); - } else { - $parts = explode(' AS ', $name); - } - - if (count($parts) > 1) { - $fieldName = $parts[1]; - } else { - $fieldName = $parts[0]; - } - - $name = strtolower($parts[0]); - } else { - $fieldName = $name; - $name = strtolower($name); - } - - $name = trim($name); - $fieldName = trim($fieldName); - - if ($prepend) { - $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames); - $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames); - } else { - $this->_columnNames[$fieldName] = $name; - $this->_fieldNames[$name] = $fieldName; - } - - if ($length == null) { - switch ($type) { - case 'string': - case 'clob': - case 'float': - case 'double': - case 'integer': - case 'array': - case 'object': - case 'blob': - case 'gzip': - // use php int max - $length = 2147483647; - break; - case 'boolean': - $length = 1; - case 'date': - // YYYY-MM-DD ISO 8601 - $length = 10; - case 'time': - // HH:NN:SS+00:00 ISO 8601 - $length = 14; - case 'timestamp': - // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601 - $length = 25; - break; - } - } - - $options['type'] = $type; - $options['length'] = $length; - - if ($prepend) { - $this->_columns = array_merge(array($name => $options), $this->_columns); - } else { - $this->_columns[$name] = $options; - } - - if (isset($options['primary']) && $options['primary']) { - if (isset($this->_identifier)) { - $this->_identifier = (array) $this->_identifier; - } - if ( ! in_array($fieldName, $this->_identifier)) { - $this->_identifier[] = $fieldName; - } - } - if (isset($options['default'])) { - $this->hasDefaultValues = true; - } - } - - public function hasDefaultValues() - { - return $this->hasDefaultValues; - } - - public function getDefaultValueOf($fieldName) - { - $columnName = $this->getColumnName($fieldName); - if ( ! isset($this->_columns[$columnName])) { - throw new IPF_ORM_Exception("Couldn't get default value. Column ".$columnName." doesn't exist."); - } - if (isset($this->_columns[$columnName]['default'])) { - return $this->_columns[$columnName]['default']; - } else { - return null; - } - } - - public function getIdentifier() - { - return $this->_identifier; - } - - public function getIdentifierType() - { - return $this->_identifierType; - } - - public function hasColumn($columnName) - { - return isset($this->_columns[strtolower($columnName)]); - } - - public function hasField($fieldName) - { - return isset($this->_columnNames[$fieldName]); - } - - public function setSubClasses(array $map) - { - $class = $this->getComponentName(); - if (isset($map[$class])) { - $this->setOption('inheritanceMap', $map[$class]); - } else { - $this->setOption('subclasses', array_keys($map)); - } - } - - public function setConnection(IPF_ORM_Connection $conn) - { - $this->_conn = $conn; - - $this->setParent($this->_conn); - - return $this; - } - - public function getConnection() - { - return $this->_conn; - } - - public function create(array $array = array()) - { - $record = new $this->_options['name']($this, true); - $record->fromArray($array); - - return $record; - } - - public function find($id, $hydrationMode = null) - { - if (is_null($id)) { - return false; - } - - $id = is_array($id) ? array_values($id) : array($id); - - $q = $this->createQuery(); - $q->where(implode(' = ? AND ', (array) $this->getIdentifier()) . ' = ?', $id) - ->limit(1); - $res = $q->fetchOne(array(), $hydrationMode); - $q->free(); - - return $res; - } - - public function findAll($hydrationMode = null) - { - $q = $this->createQuery(); - if ($this->_ordering) { - $q->orderBy(implode(', ', $this->_ordering)); - } elseif ($this->hasTemplate('IPF_ORM_Template_Orderable')) { - $q->orderBy($this->getTemplate('IPF_ORM_Template_Orderable')->getColumnName()); - } - return $q->execute(array(), $hydrationMode); - } - - public function findBySql($dql, $params = array(), $hydrationMode = null) - { - return $this->createQuery()->where($dql)->execute($params, $hydrationMode); - } - - public function findByDql($dql, $params = array(), $hydrationMode = null) - { - $parser = new IPF_ORM_Query($this->_conn); - $component = $this->getComponentName(); - $query = 'FROM ' . $component . ' WHERE ' . $dql; - - return $parser->query($query, $params, $hydrationMode); - } - - public function findOneByDql($dql, $params = array(), $hydrationMode = null) - { - $results = $this->findByDql($dql, $params, $hydrationMode); - if (is_array($results) && isset($results[0])) { - return $results[0]; - } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) { - return $results->getFirst(); - } else { - return false; - } - } - - - public function execute($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD) - { - return IPF_ORM_Manager::getInstance() - ->getQueryRegistry() - ->get($queryKey, $this->getComponentName()) - ->execute($params, $hydrationMode); - } - - public function executeOne($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD) - { - return IPF_ORM_Manager::getInstance() - ->getQueryRegistry() - ->get($queryKey, $this->getComponentName()) - ->fetchOne($params, $hydrationMode); - } - - public function clear() - { - $this->_identityMap = array(); - } - - public function addRecord(IPF_ORM_Record $record) - { - $id = implode(' ', $record->identifier()); - - if (isset($this->_identityMap[$id])) { - return false; - } - - $this->_identityMap[$id] = $record; - - return true; - } - - public function removeRecord(IPF_ORM_Record $record) - { - $id = implode(' ', $record->identifier()); - - if (isset($this->_identityMap[$id])) { - unset($this->_identityMap[$id]); - return true; - } - - return false; - } - - public function getRecord() - { - if ( ! empty($this->_data)) { - $identifierFieldNames = $this->getIdentifier(); - - if ( ! is_array($identifierFieldNames)) { - $identifierFieldNames = array($identifierFieldNames); - } - - $found = false; - foreach ($identifierFieldNames as $fieldName) { - if ( ! isset($this->_data[$fieldName])) { - // primary key column not found return new record - $found = true; - break; - } - $id[] = $this->_data[$fieldName]; - } - - if ($found) { - $recordName = $this->getComponentName(); - $record = new $recordName($this, true); - $this->_data = array(); - return $record; - } - - - $id = implode(' ', $id); - - if (isset($this->_identityMap[$id])) { - $record = $this->_identityMap[$id]; - $record->hydrate($this->_data); - } else { - $recordName = $this->getComponentName(); - $record = new $recordName($this); - $this->_identityMap[$id] = $record; - } - $this->_data = array(); - } else { - $recordName = $this->getComponentName(); - $record = new $recordName($this, true); - } - - return $record; - } - - final public function applyInheritance($where) - { - if ( ! empty($this->_options['inheritanceMap'])) { - $a = array(); - foreach ($this->_options['inheritanceMap'] as $field => $value) { - $a[] = $this->getColumnName($field) . ' = ?'; - } - $i = implode(' AND ', $a); - $where .= ' AND ' . $i; - } - return $where; - } - - public function count() - { - $a = $this->_conn->execute('SELECT COUNT(1) FROM ' . $this->_options['tableName'])->fetch(IPF_ORM::FETCH_NUM); - return current($a); - } - - public function getQueryObject() - { - $graph = new IPF_ORM_Query($this->getConnection()); - $graph->load($this->getComponentName()); - return $graph; - } - - public function getEnumValues($fieldName) - { - $columnName = $this->getColumnName($fieldName); - if (isset($this->_columns[$columnName]['values'])) { - return $this->_columns[$columnName]['values']; - } else { - return array(); - } - } - - public function enumValue($fieldName, $index) - { - if (IPF_ORM_Null::isNull($index)) { - return $index; - } - - $columnName = $this->getColumnName($fieldName); - if ( ! $this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM) - && isset($this->_columns[$columnName]['values'][$index]) - ) { - return $this->_columns[$columnName]['values'][$index]; - } - - return $index; - } - - public function enumIndex($fieldName, $value) - { - $values = $this->getEnumValues($fieldName); - - $index = array_search($value, $values); - if ($index === false || !$this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { - return $index; - } - return $value; - } - - public function getColumnCount() - { - return count($this->_columns); - } - - public function getColumns() - { - return $this->_columns; - } - - public function removeColumn($fieldName) - { - if ($this->hasField($fieldName)) { - $columnName = $this->getColumnName($fieldName); - unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]); - return true; - } else { - return false; - } - } - - public function getColumnNames(array $fieldNames = null) - { - if ($fieldNames === null) { - return array_keys($this->_columns); - } else { - $columnNames = array(); - foreach ($fieldNames as $fieldName) { - $columnNames[] = $this->getColumnName($fieldName); - } - return $columnNames; - } - } - - public function getIdentifierColumnNames() - { - return $this->getColumnNames((array) $this->getIdentifier()); - } - - public function getFieldNames() - { - return array_values($this->_fieldNames); - } - - public function getDefinitionOf($fieldName) - { - $columnName = $this->getColumnName($fieldName); - return $this->getColumnDefinition($columnName); - } - - public function getTypeOf($fieldName) - { - return $this->getTypeOfColumn($this->getColumnName($fieldName)); - } - - public function getTypeOfColumn($columnName) - { - return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false; - } - - public function setData(array $data) - { - $this->_data = $data; - } - - public function getData() - { - return $this->_data; - } - - public function prepareValue($fieldName, $value, $typeHint = null) - { - if (IPF_ORM_Null::isNull($value)) { - return $value; - } else if ($value === null) { - return null; - } else { - $type = is_null($typeHint) ? $this->getTypeOf($fieldName) : $typeHint; - - switch ($type) { - case 'integer': - case 'string'; - // don't do any casting here PHP INT_MAX is smaller than what the databases support - break; - case 'enum': - return $this->enumValue($fieldName, $value); - break; - case 'boolean': - return (boolean) $value; - break; - case 'array': - case 'object': - if (is_string($value)) { - $value = empty($value) ? null:unserialize($value); - - if ($value === false) { - throw new IPF_ORM_Exception('Unserialization of ' . $fieldName . ' failed.'); - } - return $value; - } - break; - case 'gzip': - $value = gzuncompress($value); - - if ($value === false) { - throw new IPF_ORM_Exception('Uncompressing of ' . $fieldName . ' failed.'); - } - return $value; - break; - } - } - return $value; - } - - public function getComponentName() - { - return $this->_options['name']; - } - - public function getTableName() - { - return $this->_options['tableName']; - } - - public function setTableName($tableName) - { - $this->setOption('tableName', $tableName); - } - - public function getTemplate($template) - { - if (!isset($this->_templates[$template])) - throw new IPF_ORM_Exception('Template ' . $template . ' not loaded'); - return $this->_templates[$template]; - } - - public function hasTemplate($template) - { - return isset($this->_templates[$template]); - } - - public function addTemplate($tpl, array $options=array()) - { - if (!is_object($tpl)) { - $className = 'IPF_ORM_Template_' . $tpl; - - if (class_exists($className, true)) { - $tpl = new $className($options); - } else if (class_exists($tpl, true)) { - $tpl = new $tpl($options); - } else { - throw new IPF_ORM_Record_Exception('Could not load behavior named: "' . $tpl . '"'); - } - } - - if (!($tpl instanceof IPF_ORM_Template)) { - throw new IPF_ORM_Record_Exception('Loaded behavior class is not an istance of IPF_ORM_Template.'); - } - - $className = get_class($tpl); - $this->_templates[$className] = $tpl; - - $tpl->setTableDefinition($this); - } - - public function setOrdering($ordering) - { - $this->_ordering = $ordering; - } - - public function getOrdering() - { - return $this->_ordering; - } - - public function bindQueryParts(array $queryParts) - { - $this->_options['queryParts'] = $queryParts; - return $this; - } - - public function bindQueryPart($queryPart, $value) - { - $this->_options['queryParts'][$queryPart] = $value; - return $this; - } - - public function getFieldValidators($fieldName) - { - $validators = array(); - $columnName = $this->getColumnName($fieldName); - // this loop is a dirty workaround to get the validators filtered out of - // the options, since everything is squeezed together currently - - if (!isset($this->_columns[$columnName])) - return array(); - - foreach ($this->_columns[$columnName] as $name => $args) { - if (empty($name) - || $name == 'primary' - || $name == 'protected' - || $name == 'autoincrement' - || $name == 'default' - || $name == 'values' - || $name == 'zerofill' - || $name == 'owner' - || $name == 'scale' - || $name == 'type' - || $name == 'length' - || $name == 'fixed') { - continue; - } - if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement'])) { - continue; - } - // skip it if it's explicitly set to FALSE (i.e. notnull => false) - if ($args === false) { - continue; - } - $validators[$name] = $args; - } - - return $validators; - } - - public function getBoundQueryPart($queryPart) - { - if (isset($this->_options['queryParts'][$queryPart])) - return $this->_options['queryParts'][$queryPart]; - else - return null; - } - - public function __toString() - { - return IPF_ORM_Utils::getTableAsString($this); - } - - protected function findBy($fieldName, $value, $hydrationMode = null) - { - $q = $this->createQuery()->where($fieldName . ' = ?', array($value)); - if ($this->_ordering) - $q->orderBy(implode(', ', $this->_ordering)); - return $q->execute(array(), $hydrationMode); - } - - protected function findOneBy($fieldName, $value, $hydrationMode = null) - { - $results = $this->createQuery() - ->where($fieldName . ' = ?',array($value)) - ->limit(1) - ->execute(array(), $hydrationMode); - - if (is_array($results) && isset($results[0])) { - return $results[0]; - } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) { - return $results->getFirst(); - } else { - return false; - } - } - - protected function _resolveFindByFieldName($name) - { - $fieldName = IPF_ORM_Inflector::tableize($name); - if ($this->hasColumn($name) || $this->hasField($name)) { - return $this->getFieldName($this->getColumnName($name)); - } else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) { - return $this->getFieldName($this->getColumnName($fieldName)); - } else { - return false; - } - } - - public function __call($method, $arguments) - { - if (substr($method, 0, 6) == 'findBy') { - $by = substr($method, 6, strlen($method)); - $method = 'findBy'; - } else if (substr($method, 0, 9) == 'findOneBy') { - $by = substr($method, 9, strlen($method)); - $method = 'findOneBy'; - } - - if (isset($by)) { - if ( ! isset($arguments[0])) { - throw new IPF_ORM_Exception('You must specify the value to findBy'); - } - - $fieldName = $this->_resolveFindByFieldName($by); - $hydrationMode = isset($arguments[1]) ? $arguments[1]:null; - if ($this->hasField($fieldName)) { - return $this->$method($fieldName, $arguments[0], $hydrationMode); - } else if ($this->hasRelation($by)) { - $relation = $this->getRelation($by); - - if ($relation['type'] === IPF_ORM_Relation::MANY) { - throw new IPF_ORM_Exception('Cannot findBy many relationship.'); - } - - return $this->$method($relation['local'], $arguments[0], $hydrationMode); - } else { - throw new IPF_ORM_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.'); - } - } - - throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method)); - } - - public function notifyRecordListeners($method, $event) - { - foreach ($this->listeners as $listener) - if (is_callable(array($listener, $method))) - $listener->$method($event); - } -} - diff --git a/ipf/legacy_orm/orm/table/repository.php b/ipf/legacy_orm/orm/table/repository.php deleted file mode 100644 index 1fd1557..0000000 --- a/ipf/legacy_orm/orm/table/repository.php +++ /dev/null @@ -1,77 +0,0 @@ -table = $table; - } - - public function getTable() - { - return $this->table; - } - - public function add(IPF_ORM_Record $record) - { - $oid = $record->getOID(); - - if (isset($this->registry[$oid])) { - return false; - } - $this->registry[$oid] = $record; - - return true; - } - - public function get($oid) - { - if ( ! isset($this->registry[$oid])) { - throw new IPF_ORM_Exception("Unknown object identifier"); - } - return $this->registry[$oid]; - } - - public function count() - { - return count($this->registry); - } - - public function evict($oid) - { - if ( ! isset($this->registry[$oid])) { - return false; - } - unset($this->registry[$oid]); - return true; - } - - public function evictAll() - { - $evicted = 0; - foreach ($this->registry as $oid=>$record) { - if ($this->evict($oid)) { - $evicted++; - } - } - return $evicted; - } - - public function getIterator() - { - return new ArrayIterator($this->registry); - } - - public function contains($oid) - { - return isset($this->registry[$oid]); - } - - public function loadAll() - { - $this->table->findAll(); - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/template.php b/ipf/legacy_orm/orm/template.php deleted file mode 100644 index 376a859..0000000 --- a/ipf/legacy_orm/orm/template.php +++ /dev/null @@ -1,19 +0,0 @@ -_invoker = $invoker; - } - - public function getInvoker() - { - return $this->_invoker; - } - - abstract public function setTableDefinition(IPF_ORM_Table $table); -} - diff --git a/ipf/legacy_orm/orm/template/listener/orderable.php b/ipf/legacy_orm/orm/template/listener/orderable.php deleted file mode 100644 index 157f492..0000000 --- a/ipf/legacy_orm/orm/template/listener/orderable.php +++ /dev/null @@ -1,45 +0,0 @@ -columnName = $columnName; - $this->prepend = $prepend; - } - - public function preInsert(IPF_ORM_Event $event) - { - $this->setOrderValue($event->getInvoker()); - } - - public function preUpdate(IPF_ORM_Event $event) - { - $this->setOrderValue($event->getInvoker()); - } - - private function setOrderValue($obj) - { - $columnName = $this->columnName; - if ($obj->$columnName !== null) - return; - - if ($this->prepend) { - $f = 'min'; - $d = '-'; - } else { - $f = 'max'; - $d = '+'; - } - - $res = IPF_ORM_Query::create() - ->select('coalesce('.$f.'('.$this->columnName.') '.$d.' 1, 1) as x_ord') - ->from(get_class($obj)) - ->execute(); - - $obj->$columnName = (int)$res[0]->x_ord; - } -} - diff --git a/ipf/legacy_orm/orm/template/listener/owned.php b/ipf/legacy_orm/orm/template/listener/owned.php deleted file mode 100644 index 7c99b69..0000000 --- a/ipf/legacy_orm/orm/template/listener/owned.php +++ /dev/null @@ -1,34 +0,0 @@ -columnName = $columnName; - } - - public function preInsert(IPF_ORM_Event $event) - { - $this->setOwner($event->getInvoker()); - } - - public function preUpdate(IPF_ORM_Event $event) - { - $this->setOwner($event->getInvoker()); - } - - private function setOwner($obj) - { - $columnName = $this->columnName; - if ($obj->$columnName) - return; - - $request = IPF_Project::getInstance()->request; - if ($request && !$request->user->isAnonymous()) { - $obj->$columnName = $request->user->id; - } - } -} - diff --git a/ipf/legacy_orm/orm/template/listener/sluggable.php b/ipf/legacy_orm/orm/template/listener/sluggable.php deleted file mode 100644 index eaf0735..0000000 --- a/ipf/legacy_orm/orm/template/listener/sluggable.php +++ /dev/null @@ -1,113 +0,0 @@ -_options = $options; - } - - public function preInsert(IPF_ORM_Event $event) - { - $name = $this->_options['name']; - - $record = $event->getInvoker(); - - if ( ! $record->$name) { - $record->$name = $this->buildSlug($record); - } - } - - public function preUpdate(IPF_ORM_Event $event) - { - if (false !== $this->_options['unique']) { - $name = $this->_options['name']; - - $record = $event->getInvoker(); - - if ( ! $record->$name || - (false !== $this->_options['canUpdate'] && - array_key_exists($name, $record->getModified()))) { - $record->$name = $this->buildSlug($record); - } - } - } - - protected function buildSlug($record) - { - if (empty($this->_options['fields'])) { - if (method_exists($record, 'getUniqueSlug')) { - $value = $record->getUniqueSlug($record); - } else { - $value = (string) $record; - } - } else { - if ($this->_options['unique'] === true) { - $value = $this->getUniqueSlug($record); - } else { - $value = ''; - foreach ($this->_options['fields'] as $field) { - $value .= $record->$field . ' '; - } - } - } - - $value = call_user_func_array($this->_options['builder'], array($value, $record)); - - return $value; - } - - public function getUniqueSlug($record) - { - $name = $this->_options['name']; - $slugFromFields = ''; - foreach ($this->_options['fields'] as $field) { - $slugFromFields .= $record->$field . ' '; - } - - $proposal = $record->$name ? $record->$name : $slugFromFields; - $proposal = call_user_func_array($this->_options['builder'], array($proposal, $record)); - $slug = $proposal; - - $whereString = 'r.' . $name . ' LIKE ?'; - $whereParams = array($proposal.'%'); - - if ($record->exists()) { - $identifier = $record->identifier(); - $whereString .= ' AND r.' . implode(' != ? AND r.', $record->getTable()->getIdentifierColumnNames()) . ' != ?'; - $whereParams = array_merge($whereParams, array_values($identifier)); - } - - foreach ($this->_options['uniqueBy'] as $uniqueBy) { - if (is_null($record->$uniqueBy)) { - $whereString .= ' AND r.'.$uniqueBy.' IS NULL'; - } else { - $whereString .= ' AND r.'.$uniqueBy.' = ?'; - $whereParams[] = $record->$uniqueBy; - } - } - - $query = IPF_ORM_Query::create() - ->select('r.'.$name) - ->from(get_class($record).' r') - ->where($whereString , $whereParams) - ->setHydrationMode(IPF_ORM::HYDRATE_ARRAY); - - $similarSlugResult = $query->execute(); - - $similarSlugs = array(); - foreach ($similarSlugResult as $key => $value) { - $similarSlugs[$key] = $value[$name]; - } - - $i = 1; - while (in_array($slug, $similarSlugs)) { - $slug = $proposal.'-'.$i; - $i++; - } - - return $slug; - } -} diff --git a/ipf/legacy_orm/orm/template/listener/timestampable.php b/ipf/legacy_orm/orm/template/listener/timestampable.php deleted file mode 100644 index 67afd8c..0000000 --- a/ipf/legacy_orm/orm/template/listener/timestampable.php +++ /dev/null @@ -1,48 +0,0 @@ -_options = $options; - } - - public function preInsert(IPF_ORM_Event $event) - { - if( ! $this->_options['created']['disabled']) { - $createdName = $this->_options['created']['name']; - $event->getInvoker()->$createdName = $this->getTimestamp('created'); - } - - if( ! $this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) { - $updatedName = $this->_options['updated']['name']; - $event->getInvoker()->$updatedName = $this->getTimestamp('updated'); - } - } - - public function preUpdate(IPF_ORM_Event $event) - { - if( ! $this->_options['updated']['disabled']) { - $updatedName = $this->_options['updated']['name']; - $event->getInvoker()->$updatedName = $this->getTimestamp('updated'); - } - } - - public function getTimestamp($type) - { - $options = $this->_options[$type]; - - if ($options['expression'] !== false && is_string($options['expression'])) { - return new IPF_ORM_Expression($options['expression']); - } else { - if ($options['type'] == 'date') { - return gmdate($options['format']); - } else if ($options['type'] == 'timestamp') { - return gmdate($options['format']); - } else { - return gmmktime(); - } - } - } -} diff --git a/ipf/legacy_orm/orm/template/orderable.php b/ipf/legacy_orm/orm/template/orderable.php deleted file mode 100644 index 4ed0a33..0000000 --- a/ipf/legacy_orm/orm/template/orderable.php +++ /dev/null @@ -1,33 +0,0 @@ -columnName = $options['name']; - if (array_key_exists('exclude', $options)) - $this->exclude = $options['exclude']; - if (array_key_exists('prepend', $options)) - $this->prepend = $options['prepend']; - } - } - - public function getColumnName() - { - return $this->columnName; - } - - public function setTableDefinition(IPF_ORM_Table $table) - { - $table->setColumn($this->columnName, 'integer', null, array('exclude' => $this->exclude)); - $table->addIndex($table->getOption('tableName') . '_orderable_' . $this->columnName, array('fields' => array($this->columnName))); - $table->listeners['Orderable_'.$this->columnName] = new IPF_ORM_Template_Listener_Orderable($this->columnName, $this->prepend); - } -} - diff --git a/ipf/legacy_orm/orm/template/owned.php b/ipf/legacy_orm/orm/template/owned.php deleted file mode 100644 index 8134201..0000000 --- a/ipf/legacy_orm/orm/template/owned.php +++ /dev/null @@ -1,49 +0,0 @@ -columnName = $options['column']; - if (array_key_exists('name', $options)) - $this->name = $options['name']; - if (array_key_exists('exclude', $options)) - $this->exclude = $options['exclude']; - if (array_key_exists('verbose', $options)) - $this->verbose = $options['verbose']; - } - } - - public function getColumnName() - { - return $this->columnName; - } - - public function setTableDefinition(IPF_ORM_Table $table) - { - $table->setColumn($this->columnName, 'integer', null, array( - 'exclude' => $this->exclude, - 'verbose' => $this->verbose, - )); - - $fks = $table->getOption('foreignKeys', array()); - $fks[] = array( - 'local' => $this->columnName, - 'foreign' => 'id', - 'foreignTable' => 'auth_users', - 'onUpdate' => null, - 'onDelete' => 'CASCADE', - ); - $table->setOption('foreignKeys', $fks); - - $table->listeners['Owned_'.$this->columnName] = new IPF_ORM_Template_Listener_Owned($this->columnName); - } -} - diff --git a/ipf/legacy_orm/orm/template/sluggable.php b/ipf/legacy_orm/orm/template/sluggable.php deleted file mode 100644 index 30d14ed..0000000 --- a/ipf/legacy_orm/orm/template/sluggable.php +++ /dev/null @@ -1,35 +0,0 @@ - 'slug', - 'type' => 'string', - 'length' => 255, - 'unique' => true, - 'options' => array(), - 'fields' => array(), - 'uniqueBy' => array(), - 'uniqueIndex' => true, - 'canUpdate' => false, - 'builder' => array('IPF_ORM_Inflector', 'urlize'), - 'indexName' => 'sluggable' - ); - - public function __construct(array $options = array()) - { - $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options); - } - - public function setTableDefinition(IPF_ORM_Table $table) - { - $table->setColumn($this->_options['name'], $this->_options['type'], $this->_options['length'], $this->_options['options']); - - if ($this->_options['unique'] == true && $this->_options['uniqueIndex'] == true && !empty($this->_options['fields'])) { - $indexFields = array($this->_options['name']); - $indexFields = array_merge($indexFields, $this->_options['uniqueBy']); - $table->addIndex($this->_options['indexName'], array('fields' => $indexFields, 'type' => 'unique')); - } - $table->listeners['Sluggable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Sluggable($this->_options); - } -} - diff --git a/ipf/legacy_orm/orm/template/timestampable.php b/ipf/legacy_orm/orm/template/timestampable.php deleted file mode 100644 index 117b025..0000000 --- a/ipf/legacy_orm/orm/template/timestampable.php +++ /dev/null @@ -1,42 +0,0 @@ - array( - 'name' => 'created_at', - 'type' => 'timestamp', - 'format' => 'Y-m-d H:i:s', - 'disabled' => false, - 'expression' => false, - 'options' => array('exclude' => true), - ), - 'updated' => array( - 'name' => 'updated_at', - 'type' => 'timestamp', - 'format' => 'Y-m-d H:i:s', - 'disabled' => false, - 'expression' => false, - 'onInsert' => true, - 'exclude' => true, - 'options' => array('exclude' => true), - ), - ); - - public function __construct(array $options = array()) - { - $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options); - } - - public function setTableDefinition(IPF_ORM_Table $table) - { - if (!$this->_options['created']['disabled']) { - $table->setColumn($this->_options['created']['name'], $this->_options['created']['type'], null, $this->_options['created']['options']); - } - if (!$this->_options['updated']['disabled']) { - $table->setColumn($this->_options['updated']['name'], $this->_options['updated']['type'], null, $this->_options['updated']['options']); - } - $table->listeners['Timestampable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Timestampable($this->_options); - } -} - diff --git a/ipf/legacy_orm/orm/transaction.php b/ipf/legacy_orm/orm/transaction.php deleted file mode 100644 index cc60631..0000000 --- a/ipf/legacy_orm/orm/transaction.php +++ /dev/null @@ -1,267 +0,0 @@ -_collections[] = $coll; - - return $this; - } - - public function getState() - { - switch ($this->_nestingLevel) { - case 0: - return IPF_ORM_Transaction::STATE_SLEEP; - break; - case 1: - return IPF_ORM_Transaction::STATE_ACTIVE; - break; - default: - return IPF_ORM_Transaction::STATE_BUSY; - } - } - - public function addInvalid(IPF_ORM_Record $record) - { - if (in_array($record, $this->invalid, true)) { - return false; - } - $this->invalid[] = $record; - return true; - } - - public function getInvalid() - { - return $this->invalid; - } - - public function getTransactionLevel() - { - return $this->_nestingLevel; - } - - public function getInternalTransactionLevel() - { - return $this->_internalNestingLevel; - } - - public function beginTransaction($savepoint = null) - { - if ( ! is_null($savepoint)) { - $this->savePoints[] = $savepoint; - - $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_CREATE); - - $this->conn->notifyDBListeners('preSavepointCreate', $event); - - if ( ! $event->skipOperation) { - $this->createSavePoint($savepoint); - } - - $this->conn->notifyDBListeners('postSavepointCreate', $event); - } else { - if ($this->_nestingLevel == 0) { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_BEGIN); - - $this->conn->notifyDBListeners('preTransactionBegin', $event); - - if ( ! $event->skipOperation) { - try { - $this->_doBeginTransaction(); - } catch (Exception $e) { - throw new IPF_ORM_Exception($e->getMessage()); - } - } - $this->conn->notifyDBListeners('postTransactionBegin', $event); - } - } - - $level = ++$this->_nestingLevel; - - return $level; - } - - public function commit($savepoint = null) - { - if ($this->_nestingLevel == 0) { - throw new IPF_ORM_Exception("Commit failed. There is no active transaction."); - } - - if ( ! is_null($savepoint)) { - $this->_nestingLevel -= $this->removeSavePoints($savepoint); - - $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_COMMIT); - - $this->conn->notifyDBListeners('preSavepointCommit', $event); - - if ( ! $event->skipOperation) { - $this->releaseSavePoint($savepoint); - } - - $this->conn->notifyDBListeners('postSavepointCommit', $event); - } else { - - if ($this->_nestingLevel == 1 || $this->_internalNestingLevel == 1) { - if ( ! empty($this->invalid)) { - if ($this->_internalNestingLevel == 1) { - // transaction was started by IPF_ORM, so we are responsible - // for a rollback - $this->rollback(); - $tmp = $this->invalid; - $this->invalid = array(); - throw new IPF_ORM_Exception_Validator($tmp); - } - } - if ($this->_nestingLevel == 1) { - // take snapshots of all collections used within this transaction - foreach ($this->_collections as $coll) { - $coll->takeSnapshot(); - } - $this->_collections = array(); - - $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_COMMIT); - - $this->conn->notifyDBListeners('preTransactionCommit', $event); - if ( ! $event->skipOperation) { - $this->_doCommit(); - } - $this->conn->notifyDBListeners('postTransactionCommit', $event); - } - } - - if ($this->_nestingLevel > 0) { - $this->_nestingLevel--; - } - if ($this->_internalNestingLevel > 0) { - $this->_internalNestingLevel--; - } - } - - return true; - } - - public function rollback($savepoint = null) - { - if ($this->_nestingLevel == 0) { - throw new IPF_ORM_Exception("Rollback failed. There is no active transaction."); - } - - if ($this->_internalNestingLevel > 1 || $this->_nestingLevel > 1) { - $this->_internalNestingLevel--; - $this->_nestingLevel--; - return false; - } - - if ( ! is_null($savepoint)) { - $this->_nestingLevel -= $this->removeSavePoints($savepoint); - - $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_ROLLBACK); - - $this->conn->notifyDBListeners('preSavepointRollback', $event); - - if ( ! $event->skipOperation) { - $this->rollbackSavePoint($savepoint); - } - - $this->conn->notifyDBListeners('postSavepointRollback', $event); - } else { - $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_ROLLBACK); - - $this->conn->notifyDBListeners('preTransactionRollback', $event); - - if ( ! $event->skipOperation) { - $this->_nestingLevel = 0; - $this->_internalNestingLevel = 0; - try { - $this->_doRollback(); - } catch (Exception $e) { - throw new IPF_ORM_Exception($e->getMessage()); - } - } - - $this->conn->notifyDBListeners('postTransactionRollback', $event); - } - - return true; - } - - protected function createSavePoint($savepoint) - { - throw new IPF_ORM_Exception('Savepoints not supported by this driver.'); - } - - protected function releaseSavePoint($savepoint) - { - throw new IPF_ORM_Exception('Savepoints not supported by this driver.'); - } - - protected function rollbackSavePoint($savepoint) - { - throw new IPF_ORM_Exception('Savepoints not supported by this driver.'); - } - - protected function _doRollback() - { - $this->conn->getDbh()->rollback(); - } - - protected function _doCommit() - { - $this->conn->getDbh()->commit(); - } - - protected function _doBeginTransaction() - { - $this->conn->getDbh()->beginTransaction(); - } - - private function removeSavePoints($savepoint) - { - $this->savePoints = array_values($this->savePoints); - - $found = false; - $i = 0; - - foreach ($this->savePoints as $key => $sp) { - if ( ! $found) { - if ($sp === $savepoint) { - $found = true; - } - } - if ($found) { - $i++; - unset($this->savePoints[$key]); - } - } - - return $i; - } - - public function setIsolation($isolation) - { - throw new IPF_ORM_Exception('Transaction isolation levels not supported by this driver.'); - } - - public function getIsolation() - { - throw new IPF_ORM_Exception('Fetching transaction isolation level not supported by this driver.'); - } - - public function beginInternalTransaction($savepoint = null) - { - $this->_internalNestingLevel++; - return $this->beginTransaction($savepoint); - } -} diff --git a/ipf/legacy_orm/orm/transaction/mysql.php b/ipf/legacy_orm/orm/transaction/mysql.php deleted file mode 100644 index 20597ff..0000000 --- a/ipf/legacy_orm/orm/transaction/mysql.php +++ /dev/null @@ -1,47 +0,0 @@ -conn->execute($query); - } - - protected function releaseSavePoint($savepoint) - { - $query = 'RELEASE SAVEPOINT ' . $savepoint; - - return $this->conn->execute($query); - } - - protected function rollbackSavePoint($savepoint) - { - $query = 'ROLLBACK TO SAVEPOINT ' . $savepoint; - - return $this->conn->execute($query); - } - - public function setIsolation($isolation) - { - switch ($isolation) { - case 'READ UNCOMMITTED': - case 'READ COMMITTED': - case 'REPEATABLE READ': - case 'SERIALIZABLE': - break; - default: - throw new IPF_ORM_Exception('Isolation level ' . $isolation . ' is not supported.'); - } - - $query = 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $isolation; - - return $this->conn->execute($query); - } - - public function getIsolation() - { - return $this->conn->fetchOne('SELECT @@tx_isolation'); - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/utils.php b/ipf/legacy_orm/orm/utils.php deleted file mode 100644 index e6b55f2..0000000 --- a/ipf/legacy_orm/orm/utils.php +++ /dev/null @@ -1,129 +0,0 @@ -'; - $r[] = 'IPF_ORM_Connection object'; - $r[] = 'State : ' . IPF_ORM_Utils::getConnectionStateAsString($connection->transaction->getState()); - $r[] = 'Open Transactions : ' . $connection->transaction->getTransactionLevel(); - $r[] = 'Table in memory : ' . $connection->count(); - $r[] = 'Driver name : ' . $connection->getDriverName(); - $r[] = ""; - return implode("\n",$r)."
"; - } - - public static function getValidators() - { - return array( - 'country', - 'creditcard', - 'date', - 'driver', - 'email', - 'exception', - 'future', - 'htmlcolor', - 'ip', - 'minlength', - 'nospace', - 'notblank', - 'notnull', - 'past', - 'range', - 'readonly', - 'regexp', - 'time', - 'timestamp', - 'unique', - 'unsigned', - 'usstate', - 'file', - 'image', - 'html', - 'uploadTo', - 'verbose', - ); - } - - - public static function arrayDeepMerge() - { - switch (func_num_args()) { - case 0: - return false; - case 1: - return func_get_arg(0); - case 2: - $args = func_get_args(); - $args[2] = array(); - - if (is_array($args[0]) && is_array($args[1])) - { - foreach (array_unique(array_merge(array_keys($args[0]),array_keys($args[1]))) as $key) - { - $isKey0 = array_key_exists($key, $args[0]); - $isKey1 = array_key_exists($key, $args[1]); - - if ($isKey0 && $isKey1 && is_array($args[0][$key]) && is_array($args[1][$key])) - { - $args[2][$key] = self::arrayDeepMerge($args[0][$key], $args[1][$key]); - } else if ($isKey0 && $isKey1) { - $args[2][$key] = $args[1][$key]; - } else if ( ! $isKey1) { - $args[2][$key] = $args[0][$key]; - } else if ( ! $isKey0) { - $args[2][$key] = $args[1][$key]; - } - } - - return $args[2]; - } else { - return $args[1]; - } - default: - $args = func_get_args(); - $args[1] = self::arrayDeepMerge($args[0], $args[1]); - array_shift($args); - return call_user_func_array(array('IPF', 'arrayDeepMerge'), $args); - break; - } - } - - public static function getTableAsString(IPF_ORM_Table $table) - { - $r[] = "
";
-        $r[] = "Component   : ".$table->getComponentName();
-        $r[] = "Table       : ".$table->getTableName();
-        $r[] = "
"; - - return implode("\n",$r)."
"; - } - - public static function getCollectionAsString(IPF_ORM_Collection $collection) - { - $r[] = "
";
-        $r[] = get_class($collection);
-        $r[] = 'data : ' . IPF_ORM::dump($collection->getData(), false);
-        //$r[] = 'snapshot : ' . IPF_ORM::dump($collection->getSnapshot());
-        $r[] = "
"; - return implode("\n",$r); - } -} - diff --git a/ipf/legacy_orm/orm/validator.php b/ipf/legacy_orm/orm/validator.php deleted file mode 100644 index 104d089..0000000 --- a/ipf/legacy_orm/orm/validator.php +++ /dev/null @@ -1,149 +0,0 @@ -getTable(); - - // if record is transient all fields will be validated - // if record is persistent only the modified fields will be validated - $fields = $record->exists() ? $record->getModified():$record->getData(); - foreach ($fields as $fieldName => $value) { - $this->validateField($table, $fieldName, $value, $record); - } - } - - private function validateField(IPF_ORM_Table $table, $fieldName, $value, IPF_ORM_Record $record) - { - if (IPF_ORM_Null::isNull($value)) { - $value = null; - } else if ($value instanceof IPF_ORM_Record) { - $value = $value->getIncremented(); - } - - $dataType = $table->getTypeOf($fieldName); - - // Validate field type - if (!IPF_ORM_Validator::isValidType($value, $dataType)) { - $record->addError($fieldName, 'type'); - } - - if ($dataType == 'enum') { - $enumIndex = $table->enumIndex($fieldName, $value); - if ($enumIndex === false) { - $record->addError($fieldName, 'enum'); - } - } - - // Validate field length - $definition = $table->getDefinitionOf($fieldName); - if (!$this->validateLength($value, $dataType, $definition['length'])) { - $record->addError($fieldName, 'length'); - } - - // Run all custom validators - foreach ($table->getFieldValidators($fieldName) as $validatorName => $args) { - if (!is_string($validatorName)) { - $validatorName = $args; - $args = array(); - } - - $validator = IPF_ORM_Validator::getValidator($validatorName); - $validator->invoker = $record; - $validator->field = $fieldName; - $validator->args = $args; - if (!$validator->validate($value)) { - $record->addError($fieldName, $validatorName); - } - } - } - - public function validateLength($value, $type, $maximumLength) - { - if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') { - return true; - } else if ($type == 'array' || $type == 'object') { - $length = strlen(serialize($value)); - } else if ($type == 'decimal') { - if (!$maximumLength) - $maximumLength = 18; - $length = strlen($value); - } else { - $length = strlen($value); - } - - if ($length > $maximumLength) { - return false; - } - return true; - } - - public function hasErrors() - { - return (count($this->stack) > 0); - } - - public static function isValidType($var, $type) - { - if ($var instanceof IPF_ORM_Expression) { - return true; - } else if ($var === null) { - return true; - } else if (is_object($var)) { - return $type == 'object'; - } - - switch ($type) { - case 'float': - case 'double': - case 'decimal': - return (string)$var == strval(floatval($var)); - case 'integer': - return (string)$var == strval(intval($var)); - case 'string': - return is_string($var) || is_numeric($var); - case 'blob': - case 'clob': - case 'gzip': - return is_string($var); - case 'array': - return is_array($var); - case 'object': - return is_object($var); - case 'boolean': - return is_bool($var) || (is_numeric($var) && ($var == 0 || $var == 1)); - case 'timestamp': - $validator = self::getValidator('timestamp'); - return $validator->validate($var); - case 'time': - $validator = self::getValidator('time'); - return $validator->validate($var); - case 'date': - $validator = self::getValidator('date'); - return $validator->validate($var); - case 'enum': - return is_string($var) || is_int($var); - default: - return false; - } - } -} - diff --git a/ipf/legacy_orm/orm/validator/country.php b/ipf/legacy_orm/orm/validator/country.php deleted file mode 100644 index 966ede8..0000000 --- a/ipf/legacy_orm/orm/validator/country.php +++ /dev/null @@ -1,261 +0,0 @@ - 'Andorra', - 'ae' => 'United Arab Emirates', - 'af' => 'Afghanistan', - 'ag' => 'Antigua and Barbuda', - 'ai' => 'Anguilla', - 'al' => 'Albania', - 'am' => 'Armenia', - 'an' => 'Netherlands Antilles', - 'ao' => 'Angola', - 'aq' => 'Antarctica', - 'ar' => 'Argentina', - 'as' => 'American Samoa', - 'at' => 'Austria', - 'au' => 'Australia', - 'aw' => 'Aruba', - 'az' => 'Azerbaijan', - 'ba' => 'Bosnia Hercegovina', - 'bb' => 'Barbados', - 'bd' => 'Bangladesh', - 'be' => 'Belgium', - 'bf' => 'Burkina Faso', - 'bg' => 'Bulgaria', - 'bh' => 'Bahrain', - 'bi' => 'Burundi', - 'bj' => 'Benin', - 'bm' => 'Bermuda', - 'bn' => 'Brunei Darussalam', - 'bo' => 'Bolivia', - 'br' => 'Brazil', - 'bs' => 'Bahamas', - 'bt' => 'Bhutan', - 'bv' => 'Bouvet Island', - 'bw' => 'Botswana', - 'by' => 'Belarus (Byelorussia)', - 'bz' => 'Belize', - 'ca' => 'Canada', - 'cc' => 'Cocos Islands', - 'cd' => 'Congo, The Democratic Republic of the', - 'cf' => 'Central African Republic', - 'cg' => 'Congo', - 'ch' => 'Switzerland', - 'ci' => 'Ivory Coast', - 'ck' => 'Cook Islands', - 'cl' => 'Chile', - 'cm' => 'Cameroon', - 'cn' => 'China', - 'co' => 'Colombia', - 'cr' => 'Costa Rica', - 'cs' => 'Czechoslovakia', - 'cu' => 'Cuba', - 'cv' => 'Cape Verde', - 'cx' => 'Christmas Island', - 'cy' => 'Cyprus', - 'cz' => 'Czech Republic', - 'de' => 'Germany', - 'dj' => 'Djibouti', - 'dk' => 'Denmark', - 'dm' => 'Dominica', - 'do' => 'Dominican Republic', - 'dz' => 'Algeria', - 'ec' => 'Ecuador', - 'ee' => 'Estonia', - 'eg' => 'Egypt', - 'eh' => 'Western Sahara', - 'er' => 'Eritrea', - 'es' => 'Spain', - 'et' => 'Ethiopia', - 'fi' => 'Finland', - 'fj' => 'Fiji', - 'fk' => 'Falkland Islands', - 'fm' => 'Micronesia', - 'fo' => 'Faroe Islands', - 'fr' => 'France', - 'fx' => 'France, Metropolitan FX', - 'ga' => 'Gabon', - 'gb' => 'United Kingdom (Great Britain)', - 'gd' => 'Grenada', - 'ge' => 'Georgia', - 'gf' => 'French Guiana', - 'gh' => 'Ghana', - 'gi' => 'Gibraltar', - 'gl' => 'Greenland', - 'gm' => 'Gambia', - 'gn' => 'Guinea', - 'gp' => 'Guadeloupe', - 'gq' => 'Equatorial Guinea', - 'gr' => 'Greece', - 'gs' => 'South Georgia and the South Sandwich Islands', - 'gt' => 'Guatemala', - 'gu' => 'Guam', - 'gw' => 'Guinea-bissau', - 'gy' => 'Guyana', - 'hk' => 'Hong Kong', - 'hm' => 'Heard and McDonald Islands', - 'hn' => 'Honduras', - 'hr' => 'Croatia', - 'ht' => 'Haiti', - 'hu' => 'Hungary', - 'id' => 'Indonesia', - 'ie' => 'Ireland', - 'il' => 'Israel', - 'in' => 'India', - 'io' => 'British Indian Ocean Territory', - 'iq' => 'Iraq', - 'ir' => 'Iran', - 'is' => 'Iceland', - 'it' => 'Italy', - 'jm' => 'Jamaica', - 'jo' => 'Jordan', - 'jp' => 'Japan', - 'ke' => 'Kenya', - 'kg' => 'Kyrgyzstan', - 'kh' => 'Cambodia', - 'ki' => 'Kiribati', - 'km' => 'Comoros', - 'kn' => 'Saint Kitts and Nevis', - 'kp' => 'North Korea', - 'kr' => 'South Korea', - 'kw' => 'Kuwait', - 'ky' => 'Cayman Islands', - 'kz' => 'Kazakhstan', - 'la' => 'Laos', - 'lb' => 'Lebanon', - 'lc' => 'Saint Lucia', - 'li' => 'Lichtenstein', - 'lk' => 'Sri Lanka', - 'lr' => 'Liberia', - 'ls' => 'Lesotho', - 'lt' => 'Lithuania', - 'lu' => 'Luxembourg', - 'lv' => 'Latvia', - 'ly' => 'Libya', - 'ma' => 'Morocco', - 'mc' => 'Monaco', - 'md' => 'Moldova Republic', - 'mg' => 'Madagascar', - 'mh' => 'Marshall Islands', - 'mk' => 'Macedonia, The Former Yugoslav Republic of', - 'ml' => 'Mali', - 'mm' => 'Myanmar', - 'mn' => 'Mongolia', - 'mo' => 'Macau', - 'mp' => 'Northern Mariana Islands', - 'mq' => 'Martinique', - 'mr' => 'Mauritania', - 'ms' => 'Montserrat', - 'mt' => 'Malta', - 'mu' => 'Mauritius', - 'mv' => 'Maldives', - 'mw' => 'Malawi', - 'mx' => 'Mexico', - 'my' => 'Malaysia', - 'mz' => 'Mozambique', - 'na' => 'Namibia', - 'nc' => 'New Caledonia', - 'ne' => 'Niger', - 'nf' => 'Norfolk Island', - 'ng' => 'Nigeria', - 'ni' => 'Nicaragua', - 'nl' => 'Netherlands', - 'no' => 'Norway', - 'np' => 'Nepal', - 'nr' => 'Nauru', - 'nt' => 'Neutral Zone', - 'nu' => 'Niue', - 'nz' => 'New Zealand', - 'om' => 'Oman', - 'pa' => 'Panama', - 'pe' => 'Peru', - 'pf' => 'French Polynesia', - 'pg' => 'Papua New Guinea', - 'ph' => 'Philippines', - 'pk' => 'Pakistan', - 'pl' => 'Poland', - 'pm' => 'St. Pierre and Miquelon', - 'pn' => 'Pitcairn', - 'pr' => 'Puerto Rico', - 'pt' => 'Portugal', - 'pw' => 'Palau', - 'py' => 'Paraguay', - 'qa' => 'Qatar', - 're' => 'Reunion', - 'ro' => 'Romania', - 'ru' => 'Russia', - 'rw' => 'Rwanda', - 'sa' => 'Saudi Arabia', - 'sb' => 'Solomon Islands', - 'sc' => 'Seychelles', - 'sd' => 'Sudan', - 'se' => 'Sweden', - 'sg' => 'Singapore', - 'sh' => 'St. Helena', - 'si' => 'Slovenia', - 'sj' => 'Svalbard and Jan Mayen Islands', - 'sk' => 'Slovakia (Slovak Republic)', - 'sl' => 'Sierra Leone', - 'sm' => 'San Marino', - 'sn' => 'Senegal', - 'so' => 'Somalia', - 'sr' => 'Suriname', - 'st' => 'Sao Tome and Principe', - 'sv' => 'El Salvador', - 'sy' => 'Syria', - 'sz' => 'Swaziland', - 'tc' => 'Turks and Caicos Islands', - 'td' => 'Chad', - 'tf' => 'French Southern Territories', - 'tg' => 'Togo', - 'th' => 'Thailand', - 'tj' => 'Tajikistan', - 'tk' => 'Tokelau', - 'tm' => 'Turkmenistan', - 'tn' => 'Tunisia', - 'to' => 'Tonga', - 'tp' => 'East Timor', - 'tr' => 'Turkey', - 'tt' => 'Trinidad, Tobago', - 'tv' => 'Tuvalu', - 'tw' => 'Taiwan', - 'tz' => 'Tanzania', - 'ua' => 'Ukraine', - 'ug' => 'Uganda', - 'uk' => 'United Kingdom', - 'um' => 'United States Minor Islands', - 'us' => 'United States of America', - 'uy' => 'Uruguay', - 'uz' => 'Uzbekistan', - 'va' => 'Vatican City', - 'vc' => 'Saint Vincent, Grenadines', - 've' => 'Venezuela', - 'vg' => 'Virgin Islands (British)', - 'vi' => 'Virgin Islands (USA)', - 'vn' => 'Viet Nam', - 'vu' => 'Vanuatu', - 'wf' => 'Wallis and Futuna Islands', - 'ws' => 'Samoa', - 'ye' => 'Yemen', - 'yt' => 'Mayotte', - 'yu' => 'Yugoslavia', - 'za' => 'South Africa', - 'zm' => 'Zambia', - 'zr' => 'Zaire', - 'zw' => 'Zimbabwe'); - - public static function getCountries() - { - return self::$countries; - } - - public function validate($value) - { - $value = strtolower($value); - return isset(self::$countries[$value]); - } -} - diff --git a/ipf/legacy_orm/orm/validator/creditcard.php b/ipf/legacy_orm/orm/validator/creditcard.php deleted file mode 100644 index 62609ac..0000000 --- a/ipf/legacy_orm/orm/validator/creditcard.php +++ /dev/null @@ -1,43 +0,0 @@ - 'visa', - "/^5[12345]\d{14}$/" => 'mastercard', - "/^3[47]\d{13}$/" => 'amex', - "/^6011\d{12}$/" => 'discover', - "/^30[012345]\d{11}$/" => 'diners', - "/^3[68]\d{12}$/" => 'diners', - ); - - $cardType = ''; - foreach ($card_regexes as $regex => $type) { - if (preg_match($regex, $value)) { - $cardType = $type; - break; - } - } - if (!$cardType) - return false; - - /* mod 10 checksum algorithm */ - $revcode = strrev($value); - $checksum = 0; - for ($i = 0; $i < strlen($revcode); $i++) { - $currentNum = intval($revcode[$i]); - if ($i & 1) { /* Odd position */ - $currentNum *= 2; - } - /* Split digits and add. */ - $checksum += $currentNum % 10; - if ($currentNum > 9) { - $checksum += 1; - } - } - return $checksum % 10 == 0; - } -} - diff --git a/ipf/legacy_orm/orm/validator/date.php b/ipf/legacy_orm/orm/validator/date.php deleted file mode 100644 index b0916a9..0000000 --- a/ipf/legacy_orm/orm/validator/date.php +++ /dev/null @@ -1,17 +0,0 @@ -_args[$arg])) { - return $this->_args[$arg]; - } - return null; - } - - public function __isset($arg) - { - return isset($this->_args[$arg]); - } - - public function __set($arg, $value) - { - $this->_args[$arg] = $value; - - return $this; - } - - public function getArg($arg) - { - if ( ! isset($this->_args[$arg])) { - throw new IPF_ORM_Exception_Validator('Unknown option ' . $arg); - } - return $this->_args[$arg]; - } - - public function setArg($arg, $value) - { - $this->_args[$arg] = $value; - - return $this; - } - - public function getArgs() - { - return $this->_args; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/email.php b/ipf/legacy_orm/orm/validator/email.php deleted file mode 100644 index 6e07505..0000000 --- a/ipf/legacy_orm/orm/validator/email.php +++ /dev/null @@ -1,11 +0,0 @@ -args) && isset($this->args['timezone'])) { - switch (strtolower($this->args['timezone'])) { - case 'gmt': - $now = gmdate("U") - date("Z"); - break; - default: - $now = getdate(); - break; - } - } else { - $now = getdate(); - } - - if ($now['year'] > $e[0]) { - return false; - } else if ($now['year'] == $e[0]) { - if ($now['mon'] > $e[1]) { - return false; - } else if ($now['mon'] == $e[1]) { - return $now['mday'] < $e[2]; - } else { - return true; - } - } else { - return true; - } - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/html.php b/ipf/legacy_orm/orm/validator/html.php deleted file mode 100644 index bd663d0..0000000 --- a/ipf/legacy_orm/orm/validator/html.php +++ /dev/null @@ -1,8 +0,0 @@ -args) && strlen($value) < $this->args) { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/nospace.php b/ipf/legacy_orm/orm/validator/nospace.php deleted file mode 100644 index b9f326c..0000000 --- a/ipf/legacy_orm/orm/validator/nospace.php +++ /dev/null @@ -1,9 +0,0 @@ -args) && isset($this->args['timezone'])) { - switch (strtolower($this->args['timezone'])) { - case 'gmt': - $now = gmdate("U") - date("Z"); - break; - default: - $now = getdate(); - break; - } - } else { - $now = getdate(); - } - - if ($now['year'] < $e[0]) { - return false; - } else if ($now['year'] == $e[0]) { - if ($now['mon'] < $e[1]) { - return false; - } else if ($now['mon'] == $e[1]) { - return $now['mday'] > $e[2]; - } else { - return true; - } - } else { - return true; - } - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/range.php b/ipf/legacy_orm/orm/validator/range.php deleted file mode 100644 index 2a895d3..0000000 --- a/ipf/legacy_orm/orm/validator/range.php +++ /dev/null @@ -1,15 +0,0 @@ -args[0]) && $value < $this->args[0]) { - return false; - } - if (isset($this->args[1]) && $value > $this->args[1]) { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/readonly.php b/ipf/legacy_orm/orm/validator/readonly.php deleted file mode 100644 index dac58f8..0000000 --- a/ipf/legacy_orm/orm/validator/readonly.php +++ /dev/null @@ -1,10 +0,0 @@ -invoker->getModified(); - return array_key_exists($this->field, $modified) ? false : true; - } -} diff --git a/ipf/legacy_orm/orm/validator/regexp.php b/ipf/legacy_orm/orm/validator/regexp.php deleted file mode 100644 index 3981f13..0000000 --- a/ipf/legacy_orm/orm/validator/regexp.php +++ /dev/null @@ -1,25 +0,0 @@ -args)) { - return true; - } - if (is_array($this->args)) { - foreach ($this->args as $regexp) { - if ( ! preg_match($regexp, $value)) { - return false; - } - } - return true; - } else { - if (preg_match($this->args, $value)) { - return true; - } - } - - return false; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/time.php b/ipf/legacy_orm/orm/validator/time.php deleted file mode 100644 index 7c90fd4..0000000 --- a/ipf/legacy_orm/orm/validator/time.php +++ /dev/null @@ -1,27 +0,0 @@ -= 0 && $hr <= 23 && $min >= 0 && $min <= 59 && $sec >= 0 && $sec <= 59; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/timestamp.php b/ipf/legacy_orm/orm/validator/timestamp.php deleted file mode 100644 index ab95161..0000000 --- a/ipf/legacy_orm/orm/validator/timestamp.php +++ /dev/null @@ -1,30 +0,0 @@ -validate($date)) { - return false; - } - - if ( ! $timeValidator->validate($time)) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/unique.php b/ipf/legacy_orm/orm/validator/unique.php deleted file mode 100644 index 8f03404..0000000 --- a/ipf/legacy_orm/orm/validator/unique.php +++ /dev/null @@ -1,35 +0,0 @@ -invoker->getTable(); - $pks = $table->getIdentifier(); - - if ( is_array($pks) ) { - $pks = join(',', $pks); - } - - $sql = 'SELECT ' . $pks . ' FROM ' . $table->getTableName() . ' WHERE ' . $this->field . ' = ?'; - - $values = array(); - $values[] = $value; - - // If the record is not new we need to add primary key checks because its ok if the - // unique value already exists in the database IF the record in the database is the same - // as the one that is validated here. - $state = $this->invoker->state(); - if ( ! ($state == IPF_ORM_Record::STATE_TDIRTY || $state == IPF_ORM_Record::STATE_TCLEAN)) { - foreach ((array) $table->getIdentifier() as $pk) { - $sql .= " AND {$pk} != ?"; - $values[] = $this->invoker->$pk; - } - } - - $stmt = $table->getConnection()->getDbh()->prepare($sql); - $stmt->execute($values); - - return ( ! is_array($stmt->fetch())); - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/unsigned.php b/ipf/legacy_orm/orm/validator/unsigned.php deleted file mode 100644 index 7f6cf1e..0000000 --- a/ipf/legacy_orm/orm/validator/unsigned.php +++ /dev/null @@ -1,14 +0,0 @@ - true, - 'AL' => true, - 'AR' => true, - 'AZ' => true, - 'CA' => true, - 'CO' => true, - 'CT' => true, - 'DC' => true, - 'DE' => true, - 'FL' => true, - 'GA' => true, - 'HI' => true, - 'IA' => true, - 'ID' => true, - 'IL' => true, - 'IN' => true, - 'KS' => true, - 'KY' => true, - 'LA' => true, - 'MA' => true, - 'MD' => true, - 'ME' => true, - 'MI' => true, - 'MN' => true, - 'MO' => true, - 'MS' => true, - 'MT' => true, - 'NC' => true, - 'ND' => true, - 'NE' => true, - 'NH' => true, - 'NJ' => true, - 'NM' => true, - 'NV' => true, - 'NY' => true, - 'OH' => true, - 'OK' => true, - 'OR' => true, - 'PA' => true, - 'PR' => true, - 'RI' => true, - 'SC' => true, - 'SD' => true, - 'TN' => true, - 'TX' => true, - 'UT' => true, - 'VA' => true, - 'VI' => true, - 'VT' => true, - 'WA' => true, - 'WI' => true, - 'WV' => true, - 'WY' => true - ); - public function getStates() - { - return self::$states; - } - - public function validate($value) - { - return isset(self::$states[$value]); - } -} \ No newline at end of file diff --git a/ipf/legacy_orm/orm/validator/verbose.php b/ipf/legacy_orm/orm/validator/verbose.php deleted file mode 100644 index 0a6478e..0000000 --- a/ipf/legacy_orm/orm/validator/verbose.php +++ /dev/null @@ -1,10 +0,0 @@ -modelName = $modelName; + } + + public function verbose_name() + { + return IPF_Utils::humanTitle($this->modelName); + } + + public function slug() + { + return strtolower($this->modelName); + } + + private function table() + { + return IPF_ORM::getTable($this->modelName); + } + + protected function query($searchValue, $filters) + { + if (method_exists($this->modelName, 'ordering')) { + $m = new $this->modelName; + $ord = $m->ordering(); + } elseif ($this->table()->getOrdering()) { + $ord = implode(', ', $this->table()->getOrdering()); + } elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable')) { + $ord = $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName(); + } else { + $ord = '1 desc'; + } + + $q = IPF_ORM_Query::create()->from($this->modelName)->orderby($ord); + + if ($searchValue) { + $wh = array(); + $whv = array(); + foreach ($this->_searchFields() as $f) { + $wh[] = $f.' like ?'; + $whv[] = '%'.$searchValue.'%'; + } + $q->addWhere($wh, $whv); + } + + foreach ($filters as $f) + $f->applyToQuery($q); + + return $q; + } + + protected function getItems($searchValue, $filters, $page, $pageSize) + { + $idColumn = $this->table()->getIdentifier(); + + $result = array(); + foreach ($this->query($searchValue, $filters)->limit($pageSize)->offset(($page - 1) * $pageSize)->execute() as $o) { + $id = $o->__get($idColumn); + $result[$id] = $o; + } + return $result; + } + + protected function itemsCount($searchValue, $filters) + { + return $this->query($searchValue, $filters)->count(); + } + + public function getObjectByID($id) + { + return $this->table()->find($id); + } + + public function searcheable() + { + return count($this->_searchFields()) > 0; + } + + protected function _searchFields() + { + return array(); + } + + public function list_display() + { + return $this->table()->getColumnNames(); + } + + public function renderCell($object, $column) + { + $value = $object->$column; + switch ($object->getTable()->getTypeOf($column)) { + case 'boolean': + return $value + ? '' + : ''; + case 'timestamp': + return Text::escape(IPF_Utils::formatDate($value)); + default: + return Text::escape($value); + } + } + + protected function columnTitle($column) + { + $columns = $this->table()->getColumns(); + if (array_key_exists($column, $columns) && array_key_exists('verbose', $columns[$column])) + return $columns[$column]['verbose']; + else + return parent::columnTitle($column); + } + + public function _orderable() + { + return $this->_orderableColumn() !== null; + } + + public function _orderableColumn() + { + if (method_exists($this, 'list_order')) + return $this->list_order(); + elseif ($this->table()->hasTemplate('IPF_ORM_Template_Orderable')) + return $this->table()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName(); + else + return null; + } + + /* filters */ + + protected function _listFilters() + { + return array(); + } + + public function listFilters() + { + $filters = array(); + foreach ($this->_listFilters() as $f) { + if (is_string($f)) + $f = new IPF_Admin_Model_RelationFilter($this->modelName, $f); + + $filters[] = $f; + } + return $filters; + } + + public function reorder($ids) + { + if (!$this->_orderable()) + return false; + + $ord_field = $this->_orderableColumn(); + + $table = IPF_ORM::getTable($this->modelName); + $conn = $table->getConnection(); + + $idColumn = $table->getIdentifier(); + if (is_array($idColumn)) + $idColumn = $idColumn[0]; + + $questions = str_repeat('?,', count($ids)-1) . '?'; + $query = 'SELECT ' . $conn->quoteIdentifier($ord_field) . + ' FROM ' . $conn->quoteIdentifier($table->getTableName()) . + ' WHERE ' . $conn->quoteIdentifier($idColumn) . ' IN (' . $questions . ')' . + ' ORDER BY ' . $conn->quoteIdentifier($ord_field); + $ords = $conn->fetchColumn($query, $ids); + + $i = 0; + foreach ($ids as $id) { + $item = $table->find($id); + $item[$ord_field] = $ords[$i]; + $item->save(); + $i++; + } + + return true; + } + + /* edit */ + + protected function _getForm($model, $data) + { + $extra = array( + 'user_fields' => $this->fields(), + 'inlines' => $this->inlines(), + ); + return new IPF_Admin_ModelForm($data, $model, $this->modelName, $extra); + } + + public function inlines() + { + return array(); + } + + public function saveObject($form, $model) + { + $model = $form->save(); + $idColumn = $this->table()->getIdentifier(); + return array($model->$idColumn, $model); + } + + public function deleteObject($model) + { + $model->delete(); + } + + public function fields() + { + return null; + } +} + +class IPF_Admin_ModelForm extends IPF_Form_Model +{ + private $inlines = array(); + + function __construct($data=null, $model=null, $modelClass='', $extra=array()) + { + $editMode = $model !== null; + + if (!$model) + $model = new $modelClass; + + $extra['model'] = $model; + + if ($extra['inlines']) { + foreach ($extra['inlines'] as $inlineClassName) { + $this->inlines[] = new $inlineClassName($model); + } + } + + if ($editMode) { + $extra['initial'] = $this->getFormData($model, $this->inlines); + } + + parent::__construct($data, $extra); + } + + function initFields($extra=array()) + { + parent::initFields($extra); + + if ($this->inlines) { + $this->field_groups[] = array('fields' => array_keys($this->fields)); + } + + foreach ($this->inlines as $inline) { + $name = $inline->getModelName(); + $this->fields[$name] = $inline->createField(); + + $this->field_groups[] = array( + 'label' => $inline->getLegend(), + 'fields' => array($name), + ); + } + } + + private function getFormData($o, $inlines) + { + $data = $o->getData(); + foreach ($o->getTable()->getRelations() as $rname => $rel) { + $fields = $this->fields(); + if (!$fields || in_array($rname, $fields)) { + if ($rel->getType() == IPF_ORM_Relation::MANY_AGGREGATE) { + $data[$rname] = array(); + foreach ($rel->fetchRelatedFor($o) as $ri) { + $data[$rname][] = $ri->pk(); + } + } + } + } + + foreach ($inlines as $inline) { + $objs = array(); + foreach ($inline->getObjects() as $io) { + $d = $io->getData(); + $d['id'] = $io->pk(); + $objs[] = $d; + } + $data[$inline->getModelName()] = $objs; + } + + return $data; + } + + function save($commit=true) + { + $model = parent::save($commit); + + foreach ($this->inlines as $inline) { + $this->saveInlineObject($inline, $model); + } + + return $model; + } + + private function saveInlineObject($inline, $model) + { + $objIndex = array(); + foreach ($inline->getObjects() as $obj) + $objIndex[$obj->pk()] = $obj; + + $modelName = $inline->getModelName(); + $fk_local = $inline->getFkLocal(); + + foreach ($this->cleaned_data[$modelName] as $objData) { + if (array_key_exists('id', $objData) && array_key_exists($objData['id'], $objIndex)) { + $obj = $objIndex[$objData['id']]; + if (isset($objData['is_remove'])) { + $obj->delete(); + } else { + $obj->synchronizeWithArray($objData); + $obj->save(); + } + } else { + $objData[$fk_local] = $model->id; + $obj = new $modelName; + $obj->synchronizeWithArray($objData); + $obj->save(); + } + } + } +} + +abstract class IPF_Admin_Model_Filter implements IPF_Admin_ListFilter +{ + abstract function applyToQuery($q); + + protected $title, $paramName; + + function title() + { + return $this->title; + } + + protected function link($params, $value, $title) + { + if ($value !== null) + $params[$this->paramName] = $value; + else + unset($params[$this->paramName]); + + return Tag::a() + ->attr('href', '?'.IPF_HTTP_URL::generateParams($params, false)) + ->append($title); + } +} + +class IPF_Admin_Model_RelationFilter extends IPF_Admin_Model_Filter +{ + private $relation, $selected = null; + + function __construct($modelName, $relName) + { + $this->relation = IPF_ORM::getTable($modelName)->getRelation($relName); + $this->title = 'By '.IPF_Utils::humanTitle($relName); + + $this->paramName = 'filter_'.$this->relation['local']; + } + + function setParams($params) + { + $this->selected = \PFF\Arr::get($params, $this->paramName); + } + + function applyToQuery($q) + { + // check ??? + if ($this->selected) + $q->addWhere($this->relation['local'].' = ?', array($this->selected)); + } + + function render($extraParams) + { + $ul = Tag::ul(); + + // reset filter + $ul->append(Tag::li() + ->toggleClass('selected', !$this->selected) + ->append($this->link($extraParams, null, __('All')))); + + // query related + $table = IPF_ORM::getTable($this->relation['class']); + + $query = $table->createQuery(); + if ($table->getOrdering()) { + $query->orderBy(implode(', ', $table->getOrdering())); + } elseif ($table->hasTemplate('IPF_ORM_Template_Orderable')) { + $query->orderBy($table->getTemplate('IPF_ORM_Template_Orderable')->getColumnName()); + } + + $foreign = $this->relation['foreign']; + foreach ($query->execute() as $val) { + $id = $val[$foreign]; + $name = (string)$val; + + $ul->append(Tag::li() + ->toggleClass('selected', $this->selected == $id) + ->append($this->link($extraParams, $id, $name))); + } + + return $ul->html(); + } +} + +class IPF_Admin_Model_OwnedFilter extends IPF_Admin_Model_Filter +{ + private $column, $selected = null; + + function __construct($modelName, $column, $title) + { + $this->column = $column; + $this->title = $title; + $this->paramName = 'filter_'.$this->column; + } + + function setParams($params) + { + $this->selected = \PFF\Arr::get($params, $this->paramName); + } + + function applyToQuery($q) + { + // check ??? + if ($this->selected) + $q->addWhere($this->column.' = ?', array($this->selected)); + } + + function render($extraParams) + { + $ul = Tag::ul(); + + // reset filter + $ul->append(Tag::li() + ->toggleClass('selected', !$this->selected) + ->append($this->link($extraParams, null, __('All')))); + + foreach (User::query()->fetchAll() as $val) { + $id = $val->id; + $name = (string)$val; + + $ul->append(Tag::li() + ->toggleClass('selected', $this->selected == $id) + ->append($this->link($extraParams, $id, $name))); + } + + return $ul->html(); + } +} + +class BooleanFilter extends IPF_Admin_Model_Filter +{ + private $column, $trueTitle, $falseTitle, $selected; + + public function __construct($column, $title, $trueTitle='Yes', $falseTitle='No') + { + $this->column = $column; + $this->title = $title; + $this->trueTitle = $trueTitle; + $this->falseTitle = $falseTitle; + + $this->paramName = 'filter_'.$column; + } + + function setParams($params) + { + $this->selected = \PFF\Arr::get($params, $this->paramName); + } + + function render($extraParams) + { + return Tag::ul(null, + Tag::li() + ->toggleClass('selected', $this->selected !== 'y' && $this->selected !== 'n') + ->append($this->link($extraParams, null, __('All'))), + Tag::li() + ->toggleClass('selected', $this->selected === 'y') + ->append($this->link($extraParams, 'y', $this->trueTitle)), + Tag::li() + ->toggleClass('selected', $this->selected === 'n') + ->append($this->link($extraParams, 'n', $this->falseTitle))); + } + + function applyToQuery($q) + { + switch ($this->selected) { + case 'y': + $query->addWhere($this->column); + break; + case 'n': + $query->addWhere('NOT '.$this->column); + break; + } + } +} + +class DateHierarchyListFilter extends IPF_Admin_Model_Filter +{ + private $model, $name; + private $day, $month, $year; + + function __construct($title, $modelName, $fieldName) + { + $this->title = $title; + $this->modelName = $modelName; + $this->name = $fieldName; + + $this->paramName = $fieldName; + } + + function setParams($params) + { + $date = \PFF\Arr::get($params, $this->paramName, ''); + $matches = array(); + if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $date, $matches)) { + $this->year = intval($matches[1]); + $this->month = intval($matches[2]); + $this->day = intval($matches[3]); + } + } + + function render($extraParams) + { + $ul = Tag::ul(); + + $ul->append(Tag::li() + ->toggleClass('selected', !$this->year) + ->append($this->link($extraParams, null, __('All')))); + + if ($this->year) + $ul->append($this->renderChoice($extraParams, $this->year, 0, 0, $this->year)->addClass('selected')); + + if ($this->month) + $ul->append($this->renderChoice($extraParams, $this->year, $this->month, 0, $this->monthName($this->month))->addClass('selected')); + + if ($this->day) + $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $this->day, $this->day)->addClass('selected')); + + if ($this->day) { + } elseif ($this->month) { + $days = $this->choices('DAY(' . $this->name . ')', + 'YEAR(' . $this->name . ')', $this->year, + 'MONTH(' . $this->name . ')', $this->month); + foreach ($days as $day) { + $ul->append($this->renderChoice($extraParams, $this->year, $this->month, $day, $day)); + } + } elseif ($this->year) { + $months = $this->choices('MONTH(' . $this->name . ')', + 'YEAR(' . $this->name . ')', $this->year); + foreach ($months as $month) { + $ul->append($this->renderChoice($extraParams, $this->year, $month, 0, $this->monthName($month))); + } + } else { + $years = $this->choices('YEAR(' . $this->name . ')'); + foreach ($years as $year) { + $ul->append($this->renderChoice($extraParams, $year, 0, 0, $year)); + } + } + + return $ul->html(); + } + + function applyToQuery($q) + { + if ($this->day) + $q->addWhere('DAY(' . $this->name . ') = ?', array($this->day)); + if ($this->month) + $q->addWhere('MONTH(' . $this->name . ') = ?', array($this->month)); + if ($this->year) + $q->addWhere('YEAR(' . $this->name . ') = ?', array($this->year)); + } + + private function choices(/* $what, [$expr, $value, ...]*/) + { + $args = func_get_args(); + $what = array_shift($args); + + $q = IPF_ORM_Query::create() + ->from($this->modelName) + ->select($what . ' AS value') + ->groupBy('1') + ->orderBy('1'); + + while ($args) { + $expr = array_shift($args); + $value = array_shift($args); + + $q->addWhere($expr . ' = ?', array($value)); + } + return \PFF\Arr::create($q->fetchArray())->pluck('value')->arr(); + } + + private function renderChoice($extraParams, $year, $month, $day, $label) + { + return Tag::li(null, + $this->link($extraParams, sprintf('%04d-%02d-%02d', $year, $month, $day), $label)); + } + + protected function monthName($month) + { + return date('F', mktime(0, 0, 0, $month, 1)); + } +} + diff --git a/src/adminmodelinline.php b/src/adminmodelinline.php new file mode 100644 index 0000000..bb5e4dd --- /dev/null +++ b/src/adminmodelinline.php @@ -0,0 +1,110 @@ +parentModel = $parentModel; + } + + abstract function getModelName(); + + public function getApplication() + { + foreach (IPF_Project::getInstance()->appList() as $app) + foreach (IPF_Legacy_ORM_App::appModelList($app) as $model) + if ($model == $this->getModelName()) + return $app; + return null; + } + + function getAddNum() + { + return 4; + } + + function getLegend() + { + return $this->getModelName(); + } + + function getFkName() + { + foreach($this->getTable()->getRelations() as $rel) { + if ($rel->getClass()==get_class($this->parentModel)) + return $rel->getAlias(); + } + throw new IPF_Exception(__('Cannot get fkName for '.$this->getModelName())); + } + + function getFkLocal() + { + foreach($this->getTable()->getRelations() as $rel) { + if ($rel->getClass() == get_class($this->parentModel)) + return $rel->getLocal(); + } + throw new IPF_Exception(__('Cannot get fkLocal for '.$this->getModelName())); + } + + function getObjects() + { + if (!$this->parentModel->exists()) + return array(); + + $query = IPF_ORM_Query::create() + ->from($this->getModelName()) + ->where($this->getFkLocal().'='.$this->parentModel->id); + + $o = $this->_orderableColumn(); + if ($o) + $query->orderby($o); + else + $query->orderby($this->orderby); + + return $query->execute(); + } + + public function _orderable() + { + return $this->_orderableColumn() !== null; + } + + public function _orderableColumn() + { + if ($this->getTable()->hasTemplate('IPF_ORM_Template_Orderable')) + return $this->getTable()->getTemplate('IPF_ORM_Template_Orderable')->getColumnName(); + else + return null; + } + + private function getTable() + { + return IPF_ORM::getTable($this->getModelName()); + } + + public function createField() + { + $exclude = array( + $this->getFkName(), + $this->getFkLocal(), + ); + $o = $this->_orderableColumn(); + if ($o) + $exclude[] = $o; + + $fields = array(); + foreach (IPF_Form_Model::suggestFields($this->getTable(), null, $exclude, null) as $field) { + list($n, $f) = $field; + $fields[$n] = $f; + } + + return new IPF_Form_Field_Set(array( + 'fields' => $fields, + 'addCount' => $this->getAddNum(), + )); + } +} + diff --git a/src/app.php b/src/app.php new file mode 100644 index 0000000..8db81a0 --- /dev/null +++ b/src/app.php @@ -0,0 +1,38 @@ +getName(), self::$appModels)) { + $models = array(); + try { + $it = new DirectoryIterator($app->getPath().DIRECTORY_SEPARATOR.'models'); + foreach ($it as $file) { + $e = explode('.', $file->getFileName(), 2); + if (count($e) == 2 && $e[1] === 'php') { + $models[] = $e[0]; + } + } + } catch (RuntimeException $e) { + // nothing to do + } + + self::$appModels[$app->getName()] = $models; + } + return self::$appModels[$app->getName()]; + } + + public function commands() + { + return array( + new IPF_Legacy_ORM_Command_BuildModels, + new IPF_Legacy_ORM_Command_Sql, + new IPF_Legacy_ORM_Command_SyncDB, + new IPF_Legacy_ORM_Command_Fixtures, + ); + } +} + diff --git a/src/commands/buildmodels.php b/src/commands/buildmodels.php new file mode 100644 index 0000000..9dca874 --- /dev/null +++ b/src/commands/buildmodels.php @@ -0,0 +1,21 @@ +appList() as $app) + $paths[] = $app->getPath(); + + IPF_ORM::generateModelsFromYaml($paths, array()); + } +} + diff --git a/src/commands/fixtures.php b/src/commands/fixtures.php new file mode 100644 index 0000000..3c6e279 --- /dev/null +++ b/src/commands/fixtures.php @@ -0,0 +1,68 @@ +appList() as $app) + $paths[] = $app->path; + + $fixtures = array(); + foreach ($paths as $path) { + $path .= DIRECTORY_SEPARATOR.'fixtures.php'; + if (is_file($path)) + $fixtures = array_merge($fixtures, require $path); + } + + if (!count($fixtures)) { + echo "No fixtures found\n"; + return; + } + + foreach ($fixtures as $fixture) { + $modelClass = $fixture['model']; + $key = $fixture['key']; + if (!is_array($key)) + $key = array($key); + $records = $fixture['records']; + echo "Loading $modelClass "; + $table = IPF_ORM::getTable($modelClass); + $table->getConnection()->beginTransaction(); + + $query = $table + ->createQuery() + ->limit(1); + foreach ($key as $k) + $query->addWhere($k . ' = ?'); + + foreach ($records as $record) { + $params = array(); + foreach ($key as $k) + $params[] = $record[$k]; + + $model = $query->execute($params); + if ($model) + $model = $model[0]; + else + $model = new $modelClass; + + foreach ($record as $k => $v) + $model->$k = $v; + + $model->save(); + echo '.'; + } + $table->getConnection()->commit(); + echo "\n"; + } + } +} + diff --git a/src/commands/sql.php b/src/commands/sql.php new file mode 100644 index 0000000..31f55c9 --- /dev/null +++ b/src/commands/sql.php @@ -0,0 +1,16 @@ +appList() as $app) + print IPF_ORM::generateSqlFromModels($app)."\n"; + } +} + diff --git a/src/commands/syncdb.php b/src/commands/syncdb.php new file mode 100644 index 0000000..02b550f --- /dev/null +++ b/src/commands/syncdb.php @@ -0,0 +1,21 @@ +appList() as $app) + $models = array_merge($models, IPF_Legacy_ORM_App::appModelList($app)); + + IPF_ORM::createTablesFromModels($models); + } +} + diff --git a/src/modelform.php b/src/modelform.php new file mode 100644 index 0000000..fe9cb93 --- /dev/null +++ b/src/modelform.php @@ -0,0 +1,223 @@ +model = $extra['model']; + else + throw new IPF_Exception_Form(__('Unknown model for form')); + + if (isset($extra['user_fields'])) + $this->user_fields = $extra['user_fields']; + + $exclude = \PFF\Arr::get($extra, 'exclude', array()); + $fields = self::suggestFields($this->model->getTable(), $this->fields(), $exclude, $this); + foreach ($fields as $field) { + list($n, $f) = $field; + $this->fields[$n] = $f; + } + } + + public static function suggestFields($table, $fields=null, $exclude=array(), $form=null) + { + $result = array(); + + $db_columns = $table->getColumns(); + $db_relations = $table->getRelations(); + + if ($fields === null) { + foreach ($db_columns as $name => $col) { + if (array_search($name, $exclude) !== false) + continue; + if (isset($col['exclude']) && $col['exclude']) + continue; + + if ($form && method_exists($form, 'add__'.$name.'__field')) { + $f = call_user_func(array($form, 'add__'.$name.'__field')); + if ($f) + $field = array($name, $f); + } else { + $field = self::createDBField($name, $table, $col); + } + + if ($field) + $result[] = $field; + } + + foreach ($db_relations as $name => $relation) { + if (array_search($name, $exclude) !== false) + continue; + if (isset($relation['exclude']) && $relation['exclude']) + continue; + + if ($form && method_exists($form, 'add__'.$name.'__field')) { + $f = call_user_func(array($form, 'add__'.$name.'__field')); + if ($f) + $field = array($name, $f); + } else { + $field = self::createDBRelation($name, $relation, $db_columns); + } + + if ($field) + $result[] = $field; + } + } else { + foreach ($fields as $uname) { + $field = null; + if ($form && method_exists($form, 'add__'.$uname.'__field')) { + $f = call_user_func(array($form, 'add__'.$uname.'__field')); + if ($f) + $field = array($uname, $f); + } elseif (array_key_exists($uname, $db_columns)) { + $field = self::createDBField($uname, $table, $db_columns[$uname]); + } elseif (array_key_exists($uname, $db_relations)) { + $field = self::createDBRelation($uname, $db_relations[$uname], $db_columns); + } else { + throw new IPF_Exception_Form(sprintf(__("Model '%s' has no column '%s'."), $table->getComponentName(), $uname)); + } + if ($field) + $result[] = $field; + } + } + return $result; + } + + public static function createDBField($name, $table, $col) + { + if ($name == $table->getIdentifier()) + return null; + + $required = isset($col['notblank']) && $col['notblank']; + $label = isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name); + + $params = array( + 'required' => $required, + 'label' => $label, + 'help_text' => '', + ); + + switch ($col['type']) { + case 'string': + if (isset($col['length'])) + $params['max_length'] = (int)($col['length']); + + if (isset($col['uploadTo'])) + $params['uploadTo'] = $col['uploadTo']; + + if (isset($col['email']) && $col['email']) { $form_field = new IPF_Form_Field_Email($params); } + elseif (isset($col['file']) && $col['file']) { $form_field = new IPF_Form_Field_File($params); } + elseif (isset($col['image']) && $col['image']) { $form_field = new IPF_Form_Field_Image($params); } + elseif (isset($col['html']) && $col['html']) { $form_field = new IPF_Form_Field_Html($params); } + else { $form_field = new IPF_Form_Field_Varchar($params); } + + break; + case 'boolean': + $form_field = new IPF_Form_Field_Boolean($params); + break; + case 'integer': + $params['widget_attrs'] = array('style' => 'width:140px;'); + $form_field = new IPF_Form_Field_Integer($params); + break; + case 'double': + case 'decimal': + $params['widget_attrs'] = array('style' => 'width:140px;'); + $form_field = new IPF_Form_Field_Float($params); + break; + case 'date': + $format = IPF::get('date_format'); + if ($format) + $params['widget_attrs'] = array('format' => $format); + $form_field = new IPF_Form_Field_Date($params); + break; + case 'datetime': + case 'timestamp': + $format = IPF::get('datetime_format'); + if ($format) + $params['widget_attrs'] = array('format' => $format); + $form_field = new IPF_Form_Field_Datetime($params); + break; + default: + throw new IPF_Exception_Form(__('Unsupported column type \''.$col['type'].'\'.')); + } + + if ($form_field !== null) + return array($name, $form_field); + else + return null; + } + + public static function createDBRelation($name, $relation, $db_columns) + { + $rt = $relation->getType(); + if ($rt !== IPF_ORM_Relation::ONE_AGGREGATE && $rt !== IPF_ORM_Relation::MANY_AGGREGATE) + return null; + + $lfn = $relation->getLocalFieldName(); + if (isset($db_columns[$lfn])) + $col = $db_columns[$lfn]; + else + $col = array(); + + $table = IPF_ORM::getTable($relation->getClass()); + + $params = array( + 'required' => isset($col['notblank']), + 'label' => isset($col['verbose']) ? $col['verbose'] : IPF_Utils::humanTitle($name), + 'help_text' => '', + 'table' => $table, + 'model' => $relation->getClass(), + ); + + if ($rt === IPF_ORM_Relation::ONE_AGGREGATE) { + $choices = array('--------' => ''); + $pk = $table->getIdentifier(); + foreach ($table->findAll() as $o) { + $choices[$o->__toString()] = $o->$pk; + } + + $params['choices'] = $choices; + return array($name.'_id', new IPF_Form_Field_Choice($params)); + } elseif ($rt === IPF_ORM_Relation::MANY_AGGREGATE) { + $choices = array(); + $pk = $table->getIdentifier(); + foreach ($table->findAll() as $o) { + $choices[$o->__toString()] = $o->$pk; + } + + $params['choices'] = $choices; + return array($name, new IPF_Form_Field_MultipleChoice($params)); + } else { + return null; + } + } + + function fields() + { + return $this->user_fields; + } + + function save($commit=true) + { + if (!$this->isValid()) + throw new IPF_Exception_Form(__('Cannot save the model from an invalid form.')); + + $this->model->SetFromFormData($this->cleaned_data); + $this->model->save(); + $rels = $this->model->getTable()->getRelations(); + foreach ($rels as $rname => $rel) { + if (isset($this->cleaned_data[$rname])) { + $this->model->unlink($rel->getAlias()); + if (is_array($this->cleaned_data[$rname])) { + $this->model->link($rel->getAlias(),$this->cleaned_data[$rname]); + } + } + } + return $this->model; + } +} + diff --git a/src/orm.php b/src/orm.php new file mode 100644 index 0000000..f5fb916 --- /dev/null +++ b/src/orm.php @@ -0,0 +1,255 @@ +getTable($componentName); + } + + public static function generateModelsFromYaml($directory, $extraAllwedReferences=array()) + { + $files = array(); + foreach ((array)$directory as $dir) + $files[] = $dir . '/models.yml'; + + // load schema + $import = new IPF_ORM_Import_Schema; + $definitions = $import->importSchema($files, $extraAllwedReferences); + + // build + $models = array(); + foreach ($definitions as $name => $definition) { + print " $name\n"; + $targetPath = substr($definition['input_file'], 0, -4); + + $builder = new IPF_ORM_Import_Builder; + $builder->buildRecord($definition, $targetPath); + + $models[] = $name; + } + + return $models; + } + + public static function createTablesFromModels($models) + { + IPF_ORM_Manager::connection()->export->exportClasses($models); + } + + public static function generateSqlFromModels($app) + { + $sql = IPF_ORM_Manager::connection()->export->exportSortedClassesSql(IPF_Legacy_ORM_App::appModelList($app), false); + + $build = ''; + foreach ($sql as $query) { + $build .= $query.";\n\n"; + } + return $build; + } + + public static function dump($var, $output = true, $indent = "") + { + $ret = array(); + switch (gettype($var)) { + case 'array': + $ret[] = 'Array('; + $indent .= " "; + foreach ($var as $k => $v) { + + $ret[] = $indent . $k . ' : ' . self::dump($v, false, $indent); + } + $indent = substr($indent,0, -4); + $ret[] = $indent . ")"; + break; + case 'object': + $ret[] = 'Object(' . get_class($var) . ')'; + break; + default: + $ret[] = var_export($var, true); + } + + if ($output) { + print implode("\n", $ret); + } + + return implode("\n", $ret); + } + + public static function GetObjectOr404($object, $id) + { + $obj = IPF_ORM::getTable($object)->findOneById($id); + if ($obj) + return $obj; + throw new IPF_HTTP_Error404(); + } +} + diff --git a/src/orm/access.php b/src/orm/access.php new file mode 100644 index 0000000..d0a87d2 --- /dev/null +++ b/src/orm/access.php @@ -0,0 +1,82 @@ + $v) { + $this->set($k, $v); + } + + return $this; + } + + public function __set($name, $value) + { + $this->set($name, $value); + } + + public function __get($name) + { + return $this->get($name); + } + + public function __isset($name) + { + return $this->contains($name); + } + + public function __unset($name) + { + return $this->remove($name); + } + + public function offsetExists($offset) + { + return $this->contains($offset); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } + + public function offsetSet($offset, $value) + { + if ( ! isset($offset)) { + $this->add($value); + } else { + $this->set($offset, $value); + } + } + + public function offsetUnset($offset) + { + return $this->remove($offset); + } + + public function remove($offset) + { + throw new IPF_ORM_Exception('Remove is not supported for ' . get_class($this)); + } + + public function get($offset) + { + throw new IPF_ORM_Exception('Get is not supported for ' . get_class($this)); + } + + public function set($offset, $value) + { + throw new IPF_ORM_Exception('Set is not supported for ' . get_class($this)); + } + + public function contains($offset) + { + throw new IPF_ORM_Exception('Contains is not supported for ' . get_class($this)); + } + + public function add($value) + { + throw new IPF_ORM_Exception('Add is not supported for ' . get_class($this)); + } +} diff --git a/src/orm/adapter.php b/src/orm/adapter.php new file mode 100644 index 0000000..363cade --- /dev/null +++ b/src/orm/adapter.php @@ -0,0 +1,73 @@ +_table = $table; + + if ($keyColumn === null) { + $keyColumn = $table->getBoundQueryPart('indexBy'); + } + + if ($keyColumn === null) { + $keyColumn = $table->getAttribute(IPF_ORM::ATTR_COLL_KEY); + } + + if ($keyColumn !== null) { + $this->keyColumn = $keyColumn; + } + } + + public function getTable() + { + return $this->_table; + } + + public function setData(array $data) + { + $this->data = $data; + } + + public function serialize() + { + $vars = get_object_vars($this); + + unset($vars['reference']); + unset($vars['reference_field']); + unset($vars['relation']); + unset($vars['expandable']); + unset($vars['expanded']); + unset($vars['generator']); + + $vars['_table'] = $vars['_table']->getComponentName(); + + return serialize($vars); + } + + public function unserialize($serialized) + { + $connection = IPF_ORM_Manager::connection(); + + $array = unserialize($serialized); + + foreach ($array as $name => $values) { + $this->$name = $values; + } + + $this->_table = $connection->getTable($this->_table); + + $keyColumn = isset($array['keyColumn']) ? $array['keyColumn'] : null; + if ($keyColumn === null) { + $keyColumn = $this->_table->getBoundQueryPart('indexBy'); + } + + if ($keyColumn !== null) { + $this->keyColumn = $keyColumn; + } + } + + public function setKeyColumn($column) + { + $this->keyColumn = $column; + + return $this; + } + + public function getKeyColumn() + { + return $this->keyColumn; + } + + public function getData() + { + return $this->data; + } + + public function getFirst() + { + return reset($this->data); + } + + public function getLast() + { + return end($this->data); + } + + public function end() + { + return end($this->data); + } + + public function key() + { + return key($this->data); + } + + public function setReference(IPF_ORM_Record $record, IPF_ORM_Relation $relation) + { + $this->reference = $record; + $this->relation = $relation; + + if ($relation instanceof IPF_ORM_Relation_ForeignKey || + $relation instanceof IPF_ORM_Relation_LocalKey) { + + $this->referenceField = $relation->getForeignFieldName(); + + $value = $record->get($relation->getLocalFieldName()); + + foreach ($this->data as $record) { + if ($value !== null) { + $record->set($this->referenceField, $value, false); + } else { + $record->set($this->referenceField, $this->reference, false); + } + } + } elseif ($relation instanceof IPF_ORM_Relation_Association) { + + } + } + + public function getReference() + { + return $this->reference; + } + + public function remove($key) + { + $removed = $this->data[$key]; + + unset($this->data[$key]); + return $removed; + } + + public function contains($key) + { + return isset($this->data[$key]); + } + + public function search(IPF_ORM_Record $record) + { + return array_search($record, $this->data, true); + } + + public function get($key) + { + if ( ! isset($this->data[$key])) { + $record = $this->_table->create(); + + if (isset($this->referenceField)) { + $value = $this->reference->get($this->relation->getLocalFieldName()); + + if ($value !== null) { + $record->set($this->referenceField, $value, false); + } else { + $record->set($this->referenceField, $this->reference, false); + } + } + if ($key === null) { + $this->data[] = $record; + } else { + $this->data[$key] = $record; + } + + if (isset($this->keyColumn)) { + $record->set($this->keyColumn, $key); + } + + return $record; + } + + return $this->data[$key]; + } + + public function getPrimaryKeys() + { + $list = array(); + $name = $this->_table->getIdentifier(); + + foreach ($this->data as $record) { + if (is_array($record) && isset($record[$name])) { + $list[] = $record[$name]; + } else { + $list[] = $record->getIncremented(); + } + } + return $list; + } + + public function getKeys() + { + return array_keys($this->data); + } + + public function count() + { + return count($this->data); + } + + public function set($key, $record) + { + if (isset($this->referenceField)) { + $record->set($this->referenceField, $this->reference, false); + } + + $this->data[$key] = $record; + } + + public function add($record, $key = null) + { + if (isset($this->referenceField)) { + $value = $this->reference->get($this->relation->getLocalFieldName()); + + if ($value !== null) { + $record->set($this->referenceField, $value, false); + } else { + $record->set($this->referenceField, $this->reference, false); + } + } + /** + * for some weird reason in_array cannot be used here (php bug ?) + * + * if used it results in fatal error : [ nesting level too deep ] + */ + foreach ($this->data as $val) { + if ($val === $record) { + return false; + } + } + + if (isset($key)) { + if (isset($this->data[$key])) { + return false; + } + $this->data[$key] = $record; + return true; + } + + if (isset($this->keyColumn)) { + $value = $record->get($this->keyColumn); + if ($value === null) { + throw new IPF_ORM_Exception("Couldn't create collection index. Record field '".$this->keyColumn."' was null."); + } + $this->data[$value] = $record; + } else { + $this->data[] = $record; + } + + return true; + } + + public function merge(IPF_ORM_Collection $coll) + { + $localBase = $this->getTable()->getComponentName(); + $otherBase = $coll->getTable()->getComponentName(); + + if ($otherBase != $localBase && !is_subclass_of($otherBase, $localBase) ) { + throw new IPF_ORM_Exception("Can't merge collections with incompatible record types"); + } + + foreach ($coll->getData() as $record) { + $this->add($record); + } + + return $this; + } + + public function loadRelated($name = null) + { + $list = array(); + $query = new IPF_ORM_Query($this->_table->getConnection()); + + if ( ! isset($name)) { + foreach ($this->data as $record) { + $value = $record->getIncremented(); + if ($value !== null) { + $list[] = $value; + } + } + $query->from($this->_table->getComponentName()); + $query->where($this->_table->getComponentName() . '.id IN (' . substr(str_repeat("?, ", count($list)),0,-2) . ')'); + + return $query; + } + + $rel = $this->_table->getRelation($name); + + if ($rel instanceof IPF_ORM_Relation_LocalKey || $rel instanceof IPF_ORM_Relation_ForeignKey) { + foreach ($this->data as $record) { + $list[] = $record[$rel->getLocal()]; + } + } else { + foreach ($this->data as $record) { + $value = $record->getIncremented(); + if ($value !== null) { + $list[] = $value; + } + } + } + + $dql = $rel->getRelationDql(count($list), 'collection'); + + $coll = $query->query($dql, $list); + + $this->populateRelated($name, $coll); + } + + public function populateRelated($name, IPF_ORM_Collection $coll) + { + $rel = $this->_table->getRelation($name); + $table = $rel->getTable(); + $foreign = $rel->getForeign(); + $local = $rel->getLocal(); + + if ($rel instanceof IPF_ORM_Relation_LocalKey) { + foreach ($this->data as $key => $record) { + foreach ($coll as $k => $related) { + if ($related[$foreign] == $record[$local]) { + $this->data[$key]->setRelated($name, $related); + } + } + } + } elseif ($rel instanceof IPF_ORM_Relation_ForeignKey) { + foreach ($this->data as $key => $record) { + if ( ! $record->exists()) { + continue; + } + $sub = new IPF_ORM_Collection($table); + + foreach ($coll as $k => $related) { + if ($related[$foreign] == $record[$local]) { + $sub->add($related); + $coll->remove($k); + } + } + + $this->data[$key]->setRelated($name, $sub); + } + } elseif ($rel instanceof IPF_ORM_Relation_Association) { + $identifier = $this->_table->getIdentifier(); + $asf = $rel->getAssociationFactory(); + $name = $table->getComponentName(); + + foreach ($this->data as $key => $record) { + if ( ! $record->exists()) { + continue; + } + $sub = new IPF_ORM_Collection($table); + foreach ($coll as $k => $related) { + if ($related->get($local) == $record[$identifier]) { + $sub->add($related->get($name)); + } + } + $this->data[$key]->setRelated($name, $sub); + + } + } + } + + public function getNormalIterator() + { + return new IPF_ORM_Collection_Iterator_Normal($this); + } + + public function takeSnapshot() + { + $this->_snapshot = $this->data; + + return $this; + } + + public function getSnapshot() + { + return $this->_snapshot; + } + + public function processDiff() + { + foreach (array_udiff($this->_snapshot, $this->data, array($this, "compareRecords")) as $record) { + $record->delete(); + } + + return $this; + } + + public function toArray($deep = false, $prefixKey = false) + { + $data = array(); + foreach ($this as $key => $record) { + + $key = $prefixKey ? get_class($record) . '_' .$key:$key; + + $data[$key] = $record->toArray($deep, $prefixKey); + } + + return $data; + } + + public function fromArray($array, $deep = true) + { + $data = array(); + foreach ($array as $rowKey => $row) { + $this[$rowKey]->fromArray($row, $deep); + } + } + + public function synchronizeWithArray(array $array) + { + foreach ($this as $key => $record) { + if (isset($array[$key])) { + $record->synchronizeWithArray($array[$key]); + unset($array[$key]); + } else { + // remove records that don't exist in the array + $this->remove($key); + } + } + // create new records for each new row in the array + foreach ($array as $rowKey => $row) { + $this[$rowKey]->fromArray($row); + } + } + public function synchronizeFromArray(array $array) + { + return $this->synchronizeWithArray($array); + } + + public function getDeleteDiff() + { + return array_udiff($this->_snapshot, $this->data, array($this, 'compareRecords')); + } + + public function getInsertDiff() + { + return array_udiff($this->data, $this->_snapshot, array($this, "compareRecords")); + } + + protected function compareRecords($a, $b) + { + if ($a->getOid() == $b->getOid()) { + return 0; + } + + return ($a->getOid() > $b->getOid()) ? 1 : -1; + } + + public function save(IPF_ORM_Connection $conn = null) + { + if ($conn == null) { + $conn = $this->_table->getConnection(); + } + + $conn->beginInternalTransaction(); + + $conn->transaction->addCollection($this); + + $this->processDiff(); + + foreach ($this->getData() as $key => $record) { + $record->save($conn); + } + + $conn->commit(); + + return $this; + } + + public function delete(IPF_ORM_Connection $conn = null, $clearColl = true) + { + if ($conn == null) { + $conn = $this->_table->getConnection(); + } + + $conn->beginInternalTransaction(); + $conn->transaction->addCollection($this); + + foreach ($this as $key => $record) { + $record->delete($conn); + } + + $conn->commit(); + + if ($clearColl) { + $this->clear(); + } + + return $this; + } + + public function clear() + { + $this->data = array(); + } + + public function free($deep = false) + { + foreach ($this->getData() as $key => $record) { + if ( ! ($record instanceof IPF_ORM_Null)) { + $record->free($deep); + } + } + + $this->data = array(); + + if ($this->reference) { + $this->reference->free($deep); + $this->reference = null; + } + } + + public function getIterator() + { + $data = $this->data; + return new ArrayIterator($data); + } + + public function __toString() + { + return IPF_ORM_Utils::getCollectionAsString($this); + } + + public function getRelation() + { + return $this->relation; + } + + public function __debugInfo() + { + $r = array(); + foreach ($this->data as $item) + $r[] = $item; + return $r; + } +} + diff --git a/src/orm/configurable.php b/src/orm/configurable.php new file mode 100644 index 0000000..a19ad8f --- /dev/null +++ b/src/orm/configurable.php @@ -0,0 +1,180 @@ +getAttributeFromString($attribute); + $this->_state = $attribute; + } + + if (is_string($value) && isset($stringAttribute)) { + $value = $this->getAttributeValueFromString($stringAttribute, $value); + } + + switch ($attribute) { + case IPF_ORM::ATTR_COLL_KEY: + if ( ! ($this instanceof IPF_ORM_Table)) { + throw new IPF_ORM_Exception("This attribute can only be set at table level."); + } + if ($value !== null && ! $this->hasField($value)) { + throw new IPF_ORM_Exception("Couldn't set collection key attribute. No such field '$value'."); + } + break; + case IPF_ORM::ATTR_CACHE: + case IPF_ORM::ATTR_RESULT_CACHE: + case IPF_ORM::ATTR_QUERY_CACHE: + if ($value !== null) { + if ( ! ($value instanceof IPF_ORM_Cache_Interface)) { + throw new IPF_ORM_Exception('Cache driver should implement IPF_ORM_Cache_Interface'); + } + } + break; + case IPF_ORM::ATTR_PORTABILITY: + case IPF_ORM::ATTR_DEFAULT_TABLE_TYPE: + case IPF_ORM::ATTR_EMULATE_DATABASE: + case IPF_ORM::ATTR_USE_NATIVE_ENUM: + case IPF_ORM::ATTR_EXPORT: + case IPF_ORM::ATTR_DECIMAL_PLACES: + case IPF_ORM::ATTR_LOAD_REFERENCES: + case IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE: + case IPF_ORM::ATTR_MODEL_LOADING: + case IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN: + case IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN: + case IPF_ORM::ATTR_RECURSIVE_MERGE_FIXTURES; + case IPF_ORM::ATTR_SINGULARIZE_IMPORT; + + break; + case IPF_ORM::ATTR_FIELD_CASE: + if ($value != 0 && $value != CASE_LOWER && $value != CASE_UPPER) + throw new IPF_ORM_Exception('Field case attribute should be either 0, CASE_LOWER or CASE_UPPER constant.'); + break; + default: + throw new IPF_ORM_Exception("Unknown attribute."); + } + + $this->attributes[$attribute] = $value; + } + + public function getParams($namespace = null) + { + if ($namespace == null) { + $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE); + } + + if (!isset($this->_params[$namespace])) { + return null; + } + + return $this->_params[$namespace]; + } + + public function getParamNamespaces() + { + return array_keys($this->_params); + } + + public function setParam($name, $value, $namespace = null) + { + if ($namespace == null) { + $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE); + } + + $this->_params[$namespace][$name] = $value; + + return $this; + } + + public function getParam($name, $namespace = null) + { + if ($namespace == null) { + $namespace = $this->getAttribute(IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE); + } + + if (!isset($this->_params[$name])) { + if (isset($this->parent)) { + return $this->parent->getParam($name, $namespace); + } + return null; + } + + return $this->_params[$namespace][$name]; + } + + public function getAttribute($attribute) + { + if (is_string($attribute)) { + $upper = strtoupper($attribute); + + $const = 'IPF_ORM::ATTR_' . $upper; + + if (defined($const)) { + $attribute = constant($const); + $this->_state = $attribute; + } else { + throw new IPF_ORM_Exception('Unknown attribute: "' . $attribute . '"'); + } + } + + $attribute = (int) $attribute; + + if ($attribute < 0) { + throw new IPF_ORM_Exception('Unknown attribute.'); + } + + if (isset($this->attributes[$attribute])) { + return $this->attributes[$attribute]; + } + + if (isset($this->parent)) { + return $this->parent->getAttribute($attribute); + } + return null; + } + + public function getAttributes() + { + return $this->attributes; + } + + public function setParent(IPF_ORM_Configurable $component) + { + $this->parent = $component; + } + + public function getParent() + { + return $this->parent; + } +} diff --git a/src/orm/connection.php b/src/orm/connection.php new file mode 100644 index 0000000..7fddf02 --- /dev/null +++ b/src/orm/connection.php @@ -0,0 +1,646 @@ + false, + 'expression' => false, + 'export' => false, + 'unitOfWork' => false, + ); + + protected $properties = array('sql_comments' => array(array('start' => '--', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false)), + 'identifier_quoting' => '"', + 'string_quoting' => array('start' => "'", + 'end' => "'", + 'escape' => false, + 'escape_pattern' => false), + 'wildcards' => array('%', '_'), + 'varchar_max_length' => 255, + ); + + protected $options = array(); + private static $availableDrivers = array( + 'Mysql', + 'Pgsql', + 'Oracle', + 'Informix', + 'Mssql', + 'Sqlite', + 'Firebird' + ); + protected $_count = 0; + + public $dbListeners = array(); + + public function __construct(IPF_ORM_Manager $manager, $pdo, $user = null, $pass = null) + { + $this->dbh = $pdo; + + $this->setParent($manager); + $this->dbListeners = $manager->dbListeners; + + $this->setAttribute(IPF_ORM::ATTR_CASE, IPF_ORM::CASE_NATURAL); + $this->setAttribute(IPF_ORM::ATTR_ERRMODE, IPF_ORM::ERRMODE_EXCEPTION); + + $this->notifyDBListeners('onOpen', $this); + } + + public function getOptions() + { + return $this->options; + } + + public function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + } + + public function setOption($option, $value) + { + return $this->options[$option] = $value; + } + + public function getAttribute($attribute) + { + if (is_string($attribute)) { + $stringAttribute = $attribute; + $attribute = $this->getAttributeFromString($attribute); + } + + if ($attribute >= 100) { + if ( ! isset($this->attributes[$attribute])) { + return parent::getAttribute($attribute); + } + return $this->attributes[$attribute]; + } + + try { + return $this->dbh->getAttribute($attribute); + } catch (Exception $e) { + throw new IPF_ORM_Exception('Attribute ' . $attribute . ' not found.'); + } + } + + public static function getAvailableDrivers() + { + return PDO::getAvailableDrivers(); + } + + public function setAttribute($attribute, $value) + { + if (is_string($attribute)) { + $attributeString = $attribute; + $attribute = parent::getAttributeFromString($attribute); + } + + if (is_string($value) && isset($attributeString)) { + $value = parent::getAttributeValueFromString($attributeString, $value); + } + + if ($attribute >= 100) { + parent::setAttribute($attribute, $value); + } else { + $this->dbh->setAttribute($attribute, $value); + } + + return $this; + } + + public function getName() + { + return $this->_name; + } + + public function getDriverName() + { + return $this->driverName; + } + + public function __get($name) + { + if (isset($this->properties[$name])) { + return $this->properties[$name]; + } + + if ( ! isset($this->modules[$name])) { + throw new IPF_ORM_Exception('Unknown module / property ' . $name); + } + if ($this->modules[$name] === false) { + switch ($name) { + case 'unitOfWork': + $this->modules[$name] = new IPF_ORM_Connection_UnitOfWork($this); + break; + default: + $class = 'IPF_ORM_' . ucwords($name) . '_' . $this->getDriverName(); + $this->modules[$name] = new $class($this); + } + } + + return $this->modules[$name]; + } + + public function getManager() + { + return $this->getParent(); + } + + public function getDbh() + { + return $this->dbh; + } + + public function incrementQueryCount() + { + $this->_count++; + } + + public function replace(IPF_ORM_Table $table, array $fields, array $keys) + { + if (empty($keys)) { + throw new IPF_ORM_Exception('Not specified which fields are keys'); + } + $identifier = (array) $table->getIdentifier(); + $condition = array(); + + foreach ($fields as $fieldName => $value) { + if (in_array($fieldName, $keys)) { + if ($value !== null) { + $condition[] = $table->getColumnName($fieldName) . ' = ?'; + $conditionValues[] = $value; + } + } + } + + $affectedRows = 0; + if ( ! empty($condition) && ! empty($conditionValues)) { + $query = 'DELETE FROM ' . $this->quoteIdentifier($table->getTableName()) + . ' WHERE ' . implode(' AND ', $condition); + + $affectedRows = $this->exec($query, $conditionValues); + } + + $this->insert($table, $fields); + + $affectedRows++; + + return $affectedRows; + } + + public function delete(IPF_ORM_Table $table, array $identifier) + { + $tmp = array(); + + foreach (array_keys($identifier) as $id) { + $tmp[] = $this->quoteIdentifier($table->getColumnName($id)) . ' = ?'; + } + + $query = 'DELETE FROM ' + . $this->quoteIdentifier($table->getTableName()) + . ' WHERE ' . implode(' AND ', $tmp); + + return $this->exec($query, array_values($identifier)); + } + + public function update(IPF_ORM_Table $table, array $fields, array $identifier) + { + if (empty($fields)) { + return false; + } + + $set = array(); + foreach ($fields as $fieldName => $value) { + if ($value instanceof IPF_ORM_Expression) { + $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ' . $value->getSql(); + unset($fields[$fieldName]); + } else { + $set[] = $this->quoteIdentifier($table->getColumnName($fieldName)) . ' = ?'; + } + } + + $params = array_merge(array_values($fields), array_values($identifier)); + + $sql = 'UPDATE ' . $this->quoteIdentifier($table->getTableName()) + . ' SET ' . implode(', ', $set) + . ' WHERE ' . implode(' = ? AND ', $table->getIdentifierColumnNames()) + . ' = ?'; + + return $this->exec($sql, $params); + } + + public function insert(IPF_ORM_Table $table, array $fields) + { + $tableName = $table->getTableName(); + + // column names are specified as array keys + $cols = array(); + // the query VALUES will contain either expresions (eg 'NOW()') or ? + $a = array(); + foreach ($fields as $fieldName => $value) { + $cols[] = $this->quoteIdentifier($table->getColumnName($fieldName)); + if ($value instanceof IPF_ORM_Expression) { + $a[] = $value->getSql(); + unset($fields[$fieldName]); + } else { + $a[] = '?'; + } + } + + // build the statement + $query = 'INSERT INTO ' . $this->quoteIdentifier($tableName) + . ' (' . implode(', ', $cols) . ')' + . ' VALUES (' . implode(', ', $a) . ')'; + + return $this->exec($query, array_values($fields)); + } + + public abstract function quoteIdentifier($str); + + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $k => $value) { + if (is_bool($value)) { + $item[$k] = (int) $value; + } + } + } else { + if (is_bool($item)) { + $item = (int) $item; + } + } + return $item; + } + + public function quote($input, $type=null) + { + if ($type === null) + $type = gettype($input); + + switch ($type) { + case 'integer': + case 'enum': + case 'boolean': + case 'double': + case 'float': + case 'bool': + case 'decimal': + case 'int': + return $input; + case 'array': + case 'object': + $input = serialize($input); + case 'date': + case 'time': + case 'timestamp': + case 'string': + case 'char': + case 'varchar': + case 'text': + case 'gzip': + case 'blob': + case 'clob': + return $this->getDbh()->quote($input); + default: + throw new IPF_ORM_Exception('Unsupported type \''.$type.'\'.'); + } + } + + public function escapePattern($text) + { + $q = $this->string_quoting['escape_pattern']; + $text = str_replace($q, $q . $q, $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, $q . $wildcard, $text); + } + return $text; + } + + public function setDateFormat($format = null) + { + } + + public function fetchAll($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC); + } + + public function fetchOne($statement, array $params = array(), $colnum = 0) + { + return $this->execute($statement, $params)->fetchColumn($colnum); + } + + public function fetchRow($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_ASSOC); + } + + public function fetchArray($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetch(IPF_ORM::FETCH_NUM); + } + + public function fetchColumn($statement, array $params = array(), $colnum = 0) + { + return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_COLUMN, $colnum); + } + + public function fetchAssoc($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_ASSOC); + } + + public function fetchBoth($statement, array $params = array()) + { + return $this->execute($statement, $params)->fetchAll(IPF_ORM::FETCH_BOTH); + } + + public function query($query, array $params = array(), $hydrationMode = null) + { + $parser = new IPF_ORM_Query($this); + $res = $parser->query($query, $params, $hydrationMode); + $parser->free(); + + return $res; + } + + public function prepare($statement) + { + try { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_PREPARE, $statement); + + $this->notifyDBListeners('prePrepare', $event); + + $stmt = false; + + if ( ! $event->skipOperation) { + $stmt = $this->dbh->prepare($statement); + } + + $this->notifyDBListeners('postPrepare', $event); + + return new IPF_ORM_Connection_Statement($this, $stmt); + } catch (IPF_ORM_Exception_Adapter $e) { + } catch (PDOException $e) { + } + + $this->rethrowException($e, $this); + } + + public function queryOne($query, array $params = array()) + { + $parser = new IPF_ORM_Query($this); + + $coll = $parser->query($query, $params); + if ( ! $coll->contains(0)) { + return false; + } + return $coll[0]; + } + + public function select($query, $limit = 0, $offset = 0) + { + if ($limit > 0 || $offset > 0) { + $query = $this->modifyLimitQuery($query, $limit, $offset); + } + return $this->execute($query); + } + + public function standaloneQuery($query, $params = array()) + { + return $this->execute($query, $params); + } + + public function execute($query, array $params = array()) + { + try { + if ( ! empty($params)) { + $stmt = $this->prepare($query); + $stmt->execute($params); + + return $stmt; + } else { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_QUERY, $query, $params); + + $this->notifyDBListeners('preQuery', $event); + + if ( ! $event->skipOperation) { + $stmt = $this->dbh->query($query); + $this->_count++; + } + $this->notifyDBListeners('postQuery', $event); + + return $stmt; + } + } catch (IPF_ORM_Exception_Adapter $e) { + } catch (PDOException $e) { + } + + $this->rethrowException($e, $this); + } + + public function exec($query, array $params = array()) + { + try { + if ( ! empty($params)) { + $stmt = $this->prepare($query); + $stmt->execute($params); + + return $stmt->rowCount(); + } else { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_EXEC, $query, $params); + + $this->notifyDBListeners('preExec', $event); + if ( ! $event->skipOperation) { + $count = $this->dbh->exec($query); + + $this->_count++; + } + $this->notifyDBListeners('postExec', $event); + + return $count; + } + } catch (IPF_ORM_Exception_Adapter $e) { + } catch (PDOException $e) { } + + $this->rethrowException($e, $this); + } + + public function rethrowException(Exception $e, $invoker) + { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::CONN_ERROR); + $this->notifyDBListeners('onError', $event); + + $name = 'IPF_ORM_Exception_' . $this->driverName; + $exc = new $name($e->getMessage(), (int) $e->getCode()); + if (!is_array($e->errorInfo)) { + $e->errorInfo = array(null, null, null, null); + } + $exc->processErrorInfo($e->errorInfo); + throw $exc; + } + + public function hasTable($name) + { + return isset($this->tables[$name]); + } + + public function getTable($name) + { + if (!isset($this->tables[$name])) + $this->tables[$name] = new IPF_ORM_Table($name, $this); + return $this->tables[$name]; + } + + public function getTables() + { + return $this->tables; + } + + public function getIterator() + { + return new ArrayIterator($this->tables); + } + + public function count() + { + return $this->_count; + } + + public function addTable(IPF_ORM_Table $table) + { + $name = $table->getComponentName(); + + if (isset($this->tables[$name])) { + return false; + } + $this->tables[$name] = $table; + return true; + } + + public function create($name) + { + return $this->getTable($name)->create(); + } + + public function createQuery() + { + return new IPF_ORM_Query($this); + } + + public function flush() + { + $this->beginInternalTransaction(); + $this->unitOfWork->saveAll(); + $this->commit(); + } + + public function clear() + { + foreach ($this->tables as $k => $table) { + $table->getRepository()->evictAll(); + $table->clear(); + } + } + + public function evictTables() + { + $this->tables = array(); + $this->exported = array(); + } + + public function getTransactionLevel() + { + return $this->transaction->getTransactionLevel(); + } + + public function errorCode() + { + return $this->dbh->errorCode(); + } + + public function errorInfo() + { + return $this->dbh->errorInfo(); + } + + public function getCacheDriver() + { + return $this->getResultCacheDriver(); + } + + public function getResultCacheDriver() + { + if ( ! $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE)) { + throw new IPF_ORM_Exception('Result Cache driver not initialized.'); + } + + return $this->getAttribute(IPF_ORM::ATTR_RESULT_CACHE); + } + + public function getQueryCacheDriver() + { + if ( ! $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) { + throw new IPF_ORM_Exception('Query Cache driver not initialized.'); + } + + return $this->getAttribute(IPF_ORM::ATTR_QUERY_CACHE); + } + + public function lastInsertId() + { + return $this->getDbh()->lastInsertId(); + } + + public function beginTransaction($savepoint = null) + { + return $this->transaction->beginTransaction($savepoint); + } + + public function beginInternalTransaction($savepoint = null) + { + return $this->transaction->beginInternalTransaction($savepoint); + } + + public function commit($savepoint = null) + { + return $this->transaction->commit($savepoint); + } + + public function rollback($savepoint = null) + { + $this->transaction->rollback($savepoint); + } + + public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false) + { + return $query; + } + + public function modifyLimitSubquery(IPF_ORM_Table $rootTable, $query, $limit = false, + $offset = false, $isManip = false) + { + return $this->modifyLimitQuery($query, $limit, $offset, $isManip); + } + + public function __toString() + { + return IPF_ORM_Utils::getConnectionAsString($this); + } + + public function notifyDBListeners($method, $event) + { + foreach ($this->dbListeners as $listener) + if (is_callable(array($listener, $method))) + $listener->$method($event); + } +} + diff --git a/src/orm/connection/module.php b/src/orm/connection/module.php new file mode 100644 index 0000000..b894e2d --- /dev/null +++ b/src/orm/connection/module.php @@ -0,0 +1,29 @@ +conn = $conn; + + $e = explode('_', get_class($this)); + + $this->moduleName = $e[1]; + } + + public function getConnection() + { + return $this->conn; + } + + public function getModuleName() + { + return $this->moduleName; + } +} diff --git a/src/orm/connection/mysql.php b/src/orm/connection/mysql.php new file mode 100644 index 0000000..4195f87 --- /dev/null +++ b/src/orm/connection/mysql.php @@ -0,0 +1,124 @@ +attributes[IPF_ORM::ATTR_DEFAULT_TABLE_TYPE] = 'INNODB'; + + $this->properties['string_quoting'] = array('start' => "'", + 'end' => "'", + 'escape' => '\\', + 'escape_pattern' => '\\'); + + $this->properties['identifier_quoting'] = '`'; + + $this->properties['sql_comments'] = array( + array('start' => '-- ', 'end' => "\n", 'escape' => false), + array('start' => '#', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + $this->properties['varchar_max_length'] = 255; + + parent::__construct($manager, $adapter); + } + + public function quoteIdentifier($str) + { + $quote = $this->identifier_quoting; + $q = array(); + foreach (explode('.', $str) as $s) { + if (in_array(strtoupper($s), self::$keywords)) + $q[] = $quote . str_replace($quote, $quote . $quote, $s) . $quote; + else + $q[] = $s; + } + return implode('.', $q); + } + + public function getDatabaseName() + { + return $this->fetchOne('SELECT DATABASE()'); + } + + public function replace(IPF_ORM_Table $table, array $fields, array $keys) + { + if (empty($keys)) { + throw new IPF_ORM_Exception('Not specified which fields are keys'); + } + $columns = array(); + $values = array(); + $params = array(); + foreach ($fields as $fieldName => $value) { + $columns[] = $table->getColumnName($fieldName); + $values[] = '?'; + $params[] = $value; + } + $query = 'REPLACE INTO ' . $table->getTableName() . ' (' . implode(',', $columns) . ') VALUES (' . implode(',', $values) . ')'; + return $this->exec($query, $params); + } + + public function modifyLimitQuery($query, $limit = false,$offset = false,$isManip=false) + { + $limit = (int) $limit; + $offset = (int) $offset; + + if ($limit && $offset) { + $query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } elseif ($limit && ! $offset) { + $query .= ' LIMIT ' . $limit; + } elseif ( ! $limit && $offset) { + $query .= ' LIMIT 999999999999 OFFSET ' . $offset; + } + return $query; + } +} + diff --git a/src/orm/connection/statement.php b/src/orm/connection/statement.php new file mode 100644 index 0000000..cfea7cc --- /dev/null +++ b/src/orm/connection/statement.php @@ -0,0 +1,186 @@ +_conn = $conn; + $this->_stmt = $stmt; + + if ($stmt === false) { + throw new IPF_ORM_Exception('Unknown statement object given.'); + } + } + + public function getConnection() + { + return $this->_conn; + } + public function getStatement() + { + return $this->_stmt; + } + public function getQuery() + { + return $this->_stmt->queryString; + } + + public function bindColumn($column, $param, $type = null) + { + if ($type === null) { + return $this->_stmt->bindColumn($column, $param); + } else { + return $this->_stmt->bindColumn($column, $param, $type); + } + } + + public function bindValue($param, $value, $type = null) + { + if ($type === null) { + return $this->_stmt->bindValue($param, $value); + } else { + return $this->_stmt->bindValue($param, $value, $type); + } + } + + public function bindParam($column, &$variable, $type = null, $length = null, $driverOptions = array()) + { + if ($type === null) { + return $this->_stmt->bindParam($column, $variable); + } else { + return $this->_stmt->bindParam($column, $variable, $type, $length, $driverOptions); + } + } + + public function closeCursor() + { + return $this->_stmt->closeCursor(); + } + + public function columnCount() + { + return $this->_stmt->columnCount(); + } + + public function errorCode() + { + return $this->_stmt->errorCode(); + } + + public function errorInfo() + { + return $this->_stmt->errorInfo(); + } + + public function execute($params = null) + { + try { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_EXECUTE, $this->getQuery(), $params); + $this->_conn->notifyDBListeners('preStmtExecute', $event); + + $result = true; + if ( ! $event->skipOperation) { + $result = $this->_stmt->execute($params); + $this->_conn->incrementQueryCount(); + } + + $this->_conn->notifyDBListeners('postStmtExecute', $event); + + return $result; + } catch (PDOException $e) { + } catch (IPF_ORM_Exception_Adapter $e) { + } + + $this->_conn->rethrowException($e, $this); + + return false; + } + + public function fetch($fetchMode = IPF_ORM::FETCH_BOTH, + $cursorOrientation = IPF_ORM::FETCH_ORI_NEXT, + $cursorOffset = null) + { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCH, $this->getQuery()); + + $event->fetchMode = $fetchMode; + $event->cursorOrientation = $cursorOrientation; + $event->cursorOffset = $cursorOffset; + + $data = $this->_conn->notifyDBListeners('preFetch', $event); + + if ( ! $event->skipOperation) { + $data = $this->_stmt->fetch($fetchMode, $cursorOrientation, $cursorOffset); + } + + $this->_conn->notifyDBListeners('postFetch', $event); + + return $data; + } + + public function fetchAll($fetchMode = IPF_ORM::FETCH_BOTH, + $columnIndex = null) + { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::STMT_FETCHALL, $this->getQuery()); + $event->fetchMode = $fetchMode; + $event->columnIndex = $columnIndex; + + $this->_conn->notifyDBListeners('preFetchAll', $event); + + if ( ! $event->skipOperation) { + if ($columnIndex !== null) { + $data = $this->_stmt->fetchAll($fetchMode, $columnIndex); + } else { + $data = $this->_stmt->fetchAll($fetchMode); + } + + $event->data = $data; + } + + $this->_conn->notifyDBListeners('postFetchAll', $event); + + return $data; + } + + public function fetchColumn($columnIndex = 0) + { + return $this->_stmt->fetchColumn($columnIndex); + } + + public function fetchObject($className = 'stdClass', $args = array()) + { + return $this->_stmt->fetchObject($className, $args); + } + + public function getAttribute($attribute) + { + return $this->_stmt->getAttribute($attribute); + } + + public function getColumnMeta($column) + { + return $this->_stmt->getColumnMeta($column); + } + + public function nextRowset() + { + return $this->_stmt->nextRowset(); + } + + public function rowCount() + { + return $this->_stmt->rowCount(); + } + + public function setAttribute($attribute, $value) + { + return $this->_stmt->setAttribute($attribute, $value); + } + + public function setFetchMode($mode, $arg1 = null, $arg2 = null) + { + return $this->_stmt->setFetchMode($mode, $arg1, $arg2); + } +} + diff --git a/src/orm/connection/unitofwork.php b/src/orm/connection/unitofwork.php new file mode 100644 index 0000000..301b5d7 --- /dev/null +++ b/src/orm/connection/unitofwork.php @@ -0,0 +1,574 @@ +getConnection(); + + $state = $record->state(); + if ($state === IPF_ORM_Record::STATE_LOCKED) { + return false; + } + + $record->state(IPF_ORM_Record::STATE_LOCKED); + + $conn->beginInternalTransaction(); + $saveLater = $this->saveRelated($record); + + $record->state($state); + + if ($record->isValid()) { + $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE); + $record->preSave($event); + $record->getTable()->notifyRecordListeners('preSave', $event); + $state = $record->state(); + + if ( ! $event->skipOperation) { + switch ($state) { + case IPF_ORM_Record::STATE_TDIRTY: + $this->insert($record); + break; + case IPF_ORM_Record::STATE_DIRTY: + case IPF_ORM_Record::STATE_PROXY: + $this->update($record); + break; + case IPF_ORM_Record::STATE_CLEAN: + case IPF_ORM_Record::STATE_TCLEAN: + break; + } + } + + // NOTE: what about referential integrity issues? + foreach ($record->getPendingDeletes() as $pendingDelete) { + $pendingDelete->delete(); + } + + $record->getTable()->notifyRecordListeners('postSave', $event); + $record->postSave($event); + } else { + $conn->transaction->addInvalid($record); + } + + $state = $record->state(); + + $record->state(IPF_ORM_Record::STATE_LOCKED); + + foreach ($saveLater as $fk) { + $alias = $fk->getAlias(); + + if ($record->hasReference($alias)) { + $obj = $record->$alias; + // check that the related object is not an instance of IPF_ORM_Null + if ( ! ($obj instanceof IPF_ORM_Null)) { + $obj->save($conn); + } + } + } + + // save the MANY-TO-MANY associations + $this->saveAssociations($record); + + $record->state($state); + $conn->commit(); + + return true; + } + + public function save(IPF_ORM_Record $record) + { + $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_SAVE); + + $record->preSave($event); + + $record->getTable()->notifyRecordListeners('preSave', $event); + + if ( ! $event->skipOperation) { + switch ($record->state()) { + case IPF_ORM_Record::STATE_TDIRTY: + $this->insert($record); + break; + case IPF_ORM_Record::STATE_DIRTY: + case IPF_ORM_Record::STATE_PROXY: + $this->update($record); + break; + case IPF_ORM_Record::STATE_CLEAN: + case IPF_ORM_Record::STATE_TCLEAN: + // do nothing + break; + } + } + + $record->getTable()->notifyRecordListeners('postSave', $event); + + $record->postSave($event); + } + + public function delete(IPF_ORM_Record $record) + { + $deletions = array(); + $this->_collectDeletions($record, $deletions); + return $this->_executeDeletions($deletions); + } + + private function _collectDeletions(IPF_ORM_Record $record, array &$deletions) + { + if ( ! $record->exists()) { + return; + } + + $deletions[$record->getOid()] = $record; + $this->_cascadeDelete($record, $deletions); + } + + private function _executeDeletions(array $deletions) + { + // collect class names + $classNames = array(); + foreach ($deletions as $record) { + $classNames[] = $record->getTable()->getComponentName(); + } + $classNames = array_unique($classNames); + + // order deletes + $executionOrder = $this->buildFlushTree($classNames); + + // execute + try { + $this->conn->beginInternalTransaction(); + + for ($i = count($executionOrder) - 1; $i >= 0; $i--) { + $className = $executionOrder[$i]; + $table = $this->conn->getTable($className); + + // collect identifiers + $identifierMaps = array(); + $deletedRecords = array(); + foreach ($deletions as $oid => $record) { + if ($record->getTable()->getComponentName() == $className) { + $veto = $this->_preDelete($record); + if ( ! $veto) { + $identifierMaps[] = $record->identifier(); + $deletedRecords[] = $record; + unset($deletions[$oid]); + } + } + } + + if (count($deletedRecords) < 1) { + continue; + } + + // extract query parameters (only the identifier values are of interest) + $params = array(); + $columnNames = array(); + foreach ($identifierMaps as $idMap) { + while (list($fieldName, $value) = each($idMap)) { + $params[] = $value; + $columnNames[] = $table->getColumnName($fieldName); + } + } + $columnNames = array_unique($columnNames); + + // delete + $tableName = $table->getTableName(); + $sql = "DELETE FROM " . $this->conn->quoteIdentifier($tableName) . " WHERE "; + + if ($table->isIdentifierComposite()) { + $sql .= $this->_buildSqlCompositeKeyCondition($columnNames, count($identifierMaps)); + $this->conn->exec($sql, $params); + } else { + $sql .= $this->_buildSqlSingleKeyCondition($columnNames, count($params)); + $this->conn->exec($sql, $params); + } + + // adjust state, remove from identity map and inform postDelete listeners + foreach ($deletedRecords as $record) { + $record->state(IPF_ORM_Record::STATE_TCLEAN); + $record->getTable()->removeRecord($record); + $this->_postDelete($record); + } + } + + $this->conn->commit(); + // trigger postDelete for records skipped during the deletion (veto!) + foreach ($deletions as $skippedRecord) { + $this->_postDelete($skippedRecord); + } + + return true; + } catch (Exception $e) { + $this->conn->rollback(); + throw $e; + } + } + + private function _buildSqlSingleKeyCondition($columnNames, $numRecords) + { + $idColumn = $this->conn->quoteIdentifier($columnNames[0]); + return implode(' OR ', array_fill(0, $numRecords, "$idColumn = ?")); + } + + private function _buildSqlCompositeKeyCondition($columnNames, $numRecords) + { + $singleCondition = ""; + foreach ($columnNames as $columnName) { + $columnName = $this->conn->quoteIdentifier($columnName); + if ($singleCondition === "") { + $singleCondition .= "($columnName = ?"; + } else { + $singleCondition .= " AND $columnName = ?"; + } + } + $singleCondition .= ")"; + $fullCondition = implode(' OR ', array_fill(0, $numRecords, $singleCondition)); + + return $fullCondition; + } + + protected function _cascadeDelete(IPF_ORM_Record $record, array &$deletions) + { + foreach ($record->getTable()->getRelations() as $relation) { + if ($relation->isCascadeDelete()) { + $fieldName = $relation->getAlias(); + // if it's a xToOne relation and the related object is already loaded + // we don't need to refresh. + if ( ! ($relation->getType() == IPF_ORM_Relation::ONE && isset($record->$fieldName))) { + $record->refreshRelated($relation->getAlias()); + } + $relatedObjects = $record->get($relation->getAlias()); + if ($relatedObjects instanceof IPF_ORM_Record && $relatedObjects->exists() + && ! isset($deletions[$relatedObjects->getOid()])) { + $this->_collectDeletions($relatedObjects, $deletions); + } else if ($relatedObjects instanceof IPF_ORM_Collection && count($relatedObjects) > 0) { + // cascade the delete to the other objects + foreach ($relatedObjects as $object) { + if ( ! isset($deletions[$object->getOid()])) { + $this->_collectDeletions($object, $deletions); + } + } + } + } + } + } + + public function saveRelated(IPF_ORM_Record $record) + { + $saveLater = array(); + foreach ($record->getReferences() as $k => $v) { + $rel = $record->getTable()->getRelation($k); + + $local = $rel->getLocal(); + $foreign = $rel->getForeign(); + + if ($rel instanceof IPF_ORM_Relation_ForeignKey) { + $saveLater[$k] = $rel; + } else if ($rel instanceof IPF_ORM_Relation_LocalKey) { + // ONE-TO-ONE relationship + $obj = $record->get($rel->getAlias()); + + // Protection against infinite function recursion before attempting to save + if ($obj instanceof IPF_ORM_Record && $obj->isModified()) { + $obj->save($this->conn); + + /** Can this be removed? + $id = array_values($obj->identifier()); + + foreach ((array) $rel->getLocal() as $k => $field) { + $record->set($field, $id[$k]); + } + */ + } + } + } + + return $saveLater; + } + + public function saveAssociations(IPF_ORM_Record $record) + { + foreach ($record->getReferences() as $k => $v) { + $rel = $record->getTable()->getRelation($k); + //print get_class($rel); + if ($rel instanceof IPF_ORM_Relation_Association) { + $v->save($this->conn); + + $assocTable = $rel->getAssociationTable(); + + foreach ($v->getDeleteDiff() as $r) { + $query = 'DELETE FROM ' . $assocTable->getTableName() + . ' WHERE ' . $rel->getForeign() . ' = ?' + . ' AND ' . $rel->getLocal() . ' = ?'; + + $this->conn->execute($query, array($r->getIncremented(), $record->getIncremented())); + } + + foreach ($v->getInsertDiff() as $r) { + $assocRecord = $assocTable->create(); + $assocRecord->set($assocTable->getFieldName($rel->getForeign()), $r); + $assocRecord->set($assocTable->getFieldName($rel->getLocal()), $record); + $this->saveGraph($assocRecord); + } + } + } + } + + private function _preDelete(IPF_ORM_Record $record) + { + $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE); + $record->preDelete($event); + $record->getTable()->notifyRecordListeners('preDelete', $event); + + return $event->skipOperation; + } + + private function _postDelete(IPF_ORM_Record $record) + { + $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_DELETE); + $record->postDelete($event); + $record->getTable()->notifyRecordListeners('postDelete', $event); + } + + public function saveAll() + { + // get the flush tree + $tree = $this->buildFlushTree($this->conn->getTables()); + + // save all records + foreach ($tree as $name) { + $table = $this->conn->getTable($name); + foreach ($table->getRepository() as $record) { + $this->saveGraph($record); + } + } + } + + public function update(IPF_ORM_Record $record) + { + $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_UPDATE); + $record->preUpdate($event); + $table = $record->getTable(); + $table->notifyRecordListeners('preUpdate', $event); + + if ( ! $event->skipOperation) { + $identifier = $record->identifier(); + $array = $record->getPrepared(); + $this->conn->update($table, $array, $identifier); + $record->assignIdentifier(true); + } + + $table->notifyRecordListeners('postUpdate', $event); + + $record->postUpdate($event); + + return true; + } + + public function insert(IPF_ORM_Record $record) + { + // listen the onPreInsert event + $event = new IPF_ORM_Event($record, IPF_ORM_Event::RECORD_INSERT); + $record->preInsert($event); + $table = $record->getTable(); + $table->notifyRecordListeners('preInsert', $event); + + if ( ! $event->skipOperation) { + $this->processSingleInsert($record); + } + + $table->addRecord($record); + $table->notifyRecordListeners('postInsert', $event); + $record->postInsert($event); + + return true; + } + + public function processSingleInsert(IPF_ORM_Record $record) + { + $fields = $record->getPrepared(); + $table = $record->getTable(); + + // Populate fields with a blank array so that a blank records can be inserted + if (empty($fields)) { + foreach ($table->getFieldNames() as $field) { + $fields[$field] = null; + } + } + + $identifier = (array) $table->getIdentifier(); + + $this->conn->insert($table, $fields); + + if (count($identifier) == 1 && $table->getIdentifierType() !== IPF_ORM::IDENTIFIER_NATURAL) { + $id = $this->conn->lastInsertId(); + if (!$id) + throw new IPF_ORM_Exception("Couldn't get last insert identifier."); + + $record->assignIdentifier($id); + } else { + $record->assignIdentifier(true); + } + } + + public function buildFlushTree(array $tables) + { + // determine classes to order. only necessary because the $tables param + // can contain strings or table objects... + $classesToOrder = array(); + foreach ($tables as $table) { + if ( ! ($table instanceof IPF_ORM_Table)) { + $table = $this->conn->getTable($table, false); + } + $classesToOrder[] = $table->getComponentName(); + } + $classesToOrder = array_unique($classesToOrder); + + if (count($classesToOrder) < 2) { + return $classesToOrder; + } + + // build the correct order + $flushList = array(); + foreach ($classesToOrder as $class) { + $table = $this->conn->getTable($class, false); + $currentClass = $table->getComponentName(); + + $index = array_search($currentClass, $flushList); + + if ($index === false) { + //echo "adding $currentClass to flushlist"; + $flushList[] = $currentClass; + $index = max(array_keys($flushList)); + } + + $rels = $table->getRelations(); + + // move all foreignkey relations to the beginning + foreach ($rels as $key => $rel) { + if ($rel instanceof IPF_ORM_Relation_ForeignKey) { + unset($rels[$key]); + array_unshift($rels, $rel); + } + } + + foreach ($rels as $rel) { + $relatedClassName = $rel->getTable()->getComponentName(); + + if ( ! in_array($relatedClassName, $classesToOrder)) { + continue; + } + + $relatedCompIndex = array_search($relatedClassName, $flushList); + $type = $rel->getType(); + + // skip self-referenced relations + if ($relatedClassName === $currentClass) { + continue; + } + + if ($rel instanceof IPF_ORM_Relation_ForeignKey) { + // the related component needs to come after this component in + // the list (since it holds the fk) + + if ($relatedCompIndex !== false) { + // the component is already in the list + if ($relatedCompIndex >= $index) { + // it's already in the right place + continue; + } + + unset($flushList[$index]); + // the related comp has the fk. so put "this" comp immediately + // before it in the list + array_splice($flushList, $relatedCompIndex, 0, $currentClass); + $index = $relatedCompIndex; + } else { + $flushList[] = $relatedClassName; + } + + } else if ($rel instanceof IPF_ORM_Relation_LocalKey) { + // the related component needs to come before the current component + // in the list (since this component holds the fk). + + if ($relatedCompIndex !== false) { + // already in flush list + if ($relatedCompIndex <= $index) { + // it's in the right place + continue; + } + + unset($flushList[$relatedCompIndex]); + // "this" comp has the fk. so put the related comp before it + // in the list + array_splice($flushList, $index, 0, $relatedClassName); + } else { + array_unshift($flushList, $relatedClassName); + $index++; + } + } else if ($rel instanceof IPF_ORM_Relation_Association) { + // the association class needs to come after both classes + // that are connected through it in the list (since it holds + // both fks) + + $assocTable = $rel->getAssociationFactory(); + $assocClassName = $assocTable->getComponentName(); + + if ($relatedCompIndex !== false) { + unset($flushList[$relatedCompIndex]); + } + + array_splice($flushList, $index, 0, $relatedClassName); + $index++; + + $index3 = array_search($assocClassName, $flushList); + + if ($index3 !== false) { + if ($index3 >= $index) { + continue; + } + + unset($flushList[$index]); + array_splice($flushList, $index3, 0, $assocClassName); + $index = $relatedCompIndex; + } else { + $flushList[] = $assocClassName; + } + } + } + } + + return array_values($flushList); + } + + private function _formatDataSet(IPF_ORM_Record $record) + { + $table = $record->getTable(); + $dataSet = array(); + $component = $table->getComponentName(); + $array = $record->getPrepared(); + + foreach ($table->getColumns() as $columnName => $definition) { + if ( ! isset($dataSet[$component])) { + $dataSet[$component] = array(); + } + + $fieldName = $table->getFieldName($columnName); + if (isset($definition['primary']) && $definition['primary']) { + continue; + } + + if ( ! array_key_exists($fieldName, $array)) { + continue; + } + + if (isset($definition['owner'])) { + $dataSet[$definition['owner']][$fieldName] = $array[$fieldName]; + } else { + $dataSet[$component][$fieldName] = $array[$fieldName]; + } + } + + return $dataSet; + } +} + diff --git a/src/orm/event.php b/src/orm/event.php new file mode 100644 index 0000000..c52fd29 --- /dev/null +++ b/src/orm/event.php @@ -0,0 +1,194 @@ +_invoker = $invoker; + $this->_code = $code; + $this->_query = $query; + $this->_params = $params; + } + + public function getQuery() + { + return $this->_query; + } + + public function getName() + { + switch ($this->_code) { + case self::CONN_QUERY: + return 'query'; + case self::CONN_EXEC: + return 'exec'; + case self::CONN_PREPARE: + return 'prepare'; + case self::CONN_CONNECT: + return 'connect'; + case self::CONN_CLOSE: + return 'close'; + case self::CONN_ERROR: + return 'error'; + + case self::STMT_EXECUTE: + return 'execute'; + case self::STMT_FETCH: + return 'fetch'; + case self::STMT_FETCHALL: + return 'fetch all'; + + case self::TX_BEGIN: + return 'begin'; + case self::TX_COMMIT: + return 'commit'; + case self::TX_ROLLBACK: + return 'rollback'; + + case self::SAVEPOINT_CREATE: + return 'create savepoint'; + case self::SAVEPOINT_ROLLBACK: + return 'rollback savepoint'; + case self::SAVEPOINT_COMMIT: + return 'commit savepoint'; + + case self::RECORD_DELETE: + return 'delete record'; + case self::RECORD_SAVE: + return 'save record'; + case self::RECORD_UPDATE: + return 'update record'; + case self::RECORD_INSERT: + return 'insert record'; + case self::RECORD_SERIALIZE: + return 'serialize record'; + case self::RECORD_UNSERIALIZE: + return 'unserialize record'; + case self::RECORD_DQL_SELECT: + return 'select records'; + case self::RECORD_DQL_DELETE: + return 'delete records'; + case self::RECORD_DQL_UPDATE: + return 'update records'; + } + } + + public function getCode() + { + return $this->_code; + } + + public function __get($option) + { + if ( ! isset($this->_options[$option])) { + return null; + } + + return $this->_options[$option]; + } + + public function skipOperation() + { + $this->_options['skipOperation'] = true; + + return $this; + } + + public function __set($option, $value) + { + $this->_options[$option] = $value; + + return $this; + } + + public function set($option, &$value) + { + $this->_options[$option] =& $value; + + return $this; + } + + public function start() + { + $this->_startedMicrotime = microtime(true); + } + + public function hasEnded() + { + return ($this->_endedMicrotime != null); + } + + public function end() + { + $this->_endedMicrotime = microtime(true); + + return $this; + } + + public function getInvoker() + { + return $this->_invoker; + } + + public function setInvoker($invoker) + { + $this->_invoker = $invoker; + } + + public function getParams() + { + return $this->_params; + } + + public function getElapsedSecs() + { + if (is_null($this->_endedMicrotime)) { + return false; + } + return ($this->_endedMicrotime - $this->_startedMicrotime); + } +} diff --git a/src/orm/exception.php b/src/orm/exception.php new file mode 100644 index 0000000..d7d84e9 --- /dev/null +++ b/src/orm/exception.php @@ -0,0 +1,50 @@ + 'unknown error', + IPF_ORM::ERR_ALREADY_EXISTS => 'already exists', + IPF_ORM::ERR_CANNOT_CREATE => 'can not create', + IPF_ORM::ERR_CANNOT_ALTER => 'can not alter', + IPF_ORM::ERR_CANNOT_REPLACE => 'can not replace', + IPF_ORM::ERR_CANNOT_DELETE => 'can not delete', + IPF_ORM::ERR_CANNOT_DROP => 'can not drop', + IPF_ORM::ERR_CONSTRAINT => 'constraint violation', + IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + IPF_ORM::ERR_DIVZERO => 'division by zero', + IPF_ORM::ERR_INVALID => 'invalid', + IPF_ORM::ERR_INVALID_DATE => 'invalid date or time', + IPF_ORM::ERR_INVALID_NUMBER => 'invalid number', + IPF_ORM::ERR_MISMATCH => 'mismatch', + IPF_ORM::ERR_NODBSELECTED => 'no database selected', + IPF_ORM::ERR_NOSUCHFIELD => 'no such field', + IPF_ORM::ERR_NOSUCHTABLE => 'no such table', + IPF_ORM::ERR_NOT_CAPABLE => 'IPF backend not capable', + IPF_ORM::ERR_NOT_FOUND => 'not found', + IPF_ORM::ERR_NOT_LOCKED => 'not locked', + IPF_ORM::ERR_SYNTAX => 'syntax error', + IPF_ORM::ERR_UNSUPPORTED => 'not supported', + IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row', + IPF_ORM::ERR_INVALID_DSN => 'invalid DSN', + IPF_ORM::ERR_CONNECT_FAILED => 'connect failed', + IPF_ORM::ERR_NEED_MORE_DATA => 'insufficient data supplied', + IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found', + IPF_ORM::ERR_NOSUCHDB => 'no such database', + IPF_ORM::ERR_ACCESS_VIOLATION => 'insufficient permissions', + IPF_ORM::ERR_LOADMODULE => 'error while including on demand module', + IPF_ORM::ERR_TRUNCATED => 'truncated', + IPF_ORM::ERR_DEADLOCK => 'deadlock detected', + ); + + public function errorMessage($value = null) + { + if (is_null($value)) { + return self::$_errorMessages; + } + + return isset(self::$_errorMessages[$value]) ? + self::$_errorMessages[$value] : self::$_errorMessages[IPF_ORM::ERR]; + } +} + diff --git a/src/orm/exception/adapter.php b/src/orm/exception/adapter.php new file mode 100644 index 0000000..7f8ce00 --- /dev/null +++ b/src/orm/exception/adapter.php @@ -0,0 +1,3 @@ + 'unknown error', + IPF_ORM::ERR_ALREADY_EXISTS => 'already exists', + IPF_ORM::ERR_CANNOT_CREATE => 'can not create', + IPF_ORM::ERR_CANNOT_ALTER => 'can not alter', + IPF_ORM::ERR_CANNOT_REPLACE => 'can not replace', + IPF_ORM::ERR_CANNOT_DELETE => 'can not delete', + IPF_ORM::ERR_CANNOT_DROP => 'can not drop', + IPF_ORM::ERR_CONSTRAINT => 'constraint violation', + IPF_ORM::ERR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + IPF_ORM::ERR_DIVZERO => 'division by zero', + IPF_ORM::ERR_INVALID => 'invalid', + IPF_ORM::ERR_INVALID_DATE => 'invalid date or time', + IPF_ORM::ERR_INVALID_NUMBER => 'invalid number', + IPF_ORM::ERR_MISMATCH => 'mismatch', + IPF_ORM::ERR_NODBSELECTED => 'no database selected', + IPF_ORM::ERR_NOSUCHFIELD => 'no such field', + IPF_ORM::ERR_NOSUCHTABLE => 'no such table', + IPF_ORM::ERR_NOT_CAPABLE => 'IPF_ORM backend not capable', + IPF_ORM::ERR_NOT_FOUND => 'not found', + IPF_ORM::ERR_NOT_LOCKED => 'not locked', + IPF_ORM::ERR_SYNTAX => 'syntax error', + IPF_ORM::ERR_UNSUPPORTED => 'not supported', + IPF_ORM::ERR_VALUE_COUNT_ON_ROW => 'value count on row', + IPF_ORM::ERR_INVALID_DSN => 'invalid DSN', + IPF_ORM::ERR_CONNECT_FAILED => 'connect failed', + IPF_ORM::ERR_NEED_MORE_DATA => 'insufficient data supplied', + IPF_ORM::ERR_EXTENSION_NOT_FOUND=> 'extension not found', + IPF_ORM::ERR_NOSUCHDB => 'no such database', + IPF_ORM::ERR_ACCESS_VIOLATION => 'insufficient permissions', + IPF_ORM::ERR_LOADMODULE => 'error while including on demand module', + IPF_ORM::ERR_TRUNCATED => 'truncated', + IPF_ORM::ERR_DEADLOCK => 'deadlock detected', + ); + + protected $portableCode; + + public function getPortableCode() + { + return $this->portableCode; + } + + public function getPortableMessage() + { + return self::errorMessage($this->portableCode); + } + + public function errorMessage($value = null) + { + return isset(self::$errorMessages[$value]) ? + self::$errorMessages[$value] : self::$errorMessages[IPF_ORM::ERR]; + } + + public function processErrorInfo(array $errorInfo) + { } +} \ No newline at end of file diff --git a/src/orm/exception/locator.php b/src/orm/exception/locator.php new file mode 100644 index 0000000..d39308f --- /dev/null +++ b/src/orm/exception/locator.php @@ -0,0 +1,3 @@ + IPF_ORM::ERR_CANNOT_CREATE, + 1005 => IPF_ORM::ERR_CANNOT_CREATE, + 1006 => IPF_ORM::ERR_CANNOT_CREATE, + 1007 => IPF_ORM::ERR_ALREADY_EXISTS, + 1008 => IPF_ORM::ERR_CANNOT_DROP, + 1022 => IPF_ORM::ERR_ALREADY_EXISTS, + 1044 => IPF_ORM::ERR_ACCESS_VIOLATION, + 1046 => IPF_ORM::ERR_NODBSELECTED, + 1048 => IPF_ORM::ERR_CONSTRAINT, + 1049 => IPF_ORM::ERR_NOSUCHDB, + 1050 => IPF_ORM::ERR_ALREADY_EXISTS, + 1051 => IPF_ORM::ERR_NOSUCHTABLE, + 1054 => IPF_ORM::ERR_NOSUCHFIELD, + 1061 => IPF_ORM::ERR_ALREADY_EXISTS, + 1062 => IPF_ORM::ERR_ALREADY_EXISTS, + 1064 => IPF_ORM::ERR_SYNTAX, + 1091 => IPF_ORM::ERR_NOT_FOUND, + 1100 => IPF_ORM::ERR_NOT_LOCKED, + 1136 => IPF_ORM::ERR_VALUE_COUNT_ON_ROW, + 1142 => IPF_ORM::ERR_ACCESS_VIOLATION, + 1146 => IPF_ORM::ERR_NOSUCHTABLE, + 1216 => IPF_ORM::ERR_CONSTRAINT, + 1217 => IPF_ORM::ERR_CONSTRAINT, + 1451 => IPF_ORM::ERR_CONSTRAINT, + ); + public function processErrorInfo(array $errorInfo) + { + $code = $errorInfo[1]; + if (isset(self::$errorCodeMap[$code])) { + $this->portableCode = self::$errorCodeMap[$code]; + return true; + } + return false; + } +} diff --git a/src/orm/exception/profiler.php b/src/orm/exception/profiler.php new file mode 100644 index 0000000..5fa9ad5 --- /dev/null +++ b/src/orm/exception/profiler.php @@ -0,0 +1,3 @@ +invalid = $invalid; + parent::__construct($this->generateMessage()); + } + + public function getInvalidRecords() + { + return $this->invalid; + } + + private function generateMessage() + { + $message = ""; + foreach ($this->invalid as $record) { + $errors = array(); + foreach ($record->getErrors() as $field => $validators) + $errors[] = 'Field "' . $field . '" failed following validators: ' . implode(', ', $validators) . '.'; + $message .= "Validaton error in class " . get_class($record) . ' (' . implode(' ', $errors) . ') '; + } + return $message; + } +} + diff --git a/src/orm/export.php b/src/orm/export.php new file mode 100644 index 0000000..a0cae94 --- /dev/null +++ b/src/orm/export.php @@ -0,0 +1,479 @@ + '', + 'boolean' => true, + 'integer' => 0, + 'decimal' => 0.0, + 'float' => 0.0, + 'double' => 0.0, + 'timestamp' => '1970-01-01 00:00:00', + 'time' => '00:00:00', + 'date' => '1970-01-01', + 'clob' => '', + 'blob' => '', + 'string' => '' + ); + + protected function getIndexName($name) + { + return $name . '_idx'; + } + + public function dropTableSql($table) + { + return 'DROP TABLE ' . $this->conn->quoteIdentifier($table); + } + + public function dropTable($table) + { + $this->conn->execute($this->dropTableSql($table)); + } + + public function dropIndex($table, $name) + { + return $this->conn->exec($this->dropIndexSql($table, $name)); + } + + public function dropIndexSql($table, $name) + { + $name = $this->conn->quoteIdentifier($this->getIndexName($name)); + + return 'DROP INDEX ' . $name; + } + + public function dropConstraint($table, $name, $primary = false) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($name); + + return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name); + } + + public function dropForeignKey($table, $name) + { + return $this->dropConstraint($table, $name); + } + + public function createConstraint($table, $name, $definition) + { + $sql = $this->createConstraintSql($table, $name, $definition); + + return $this->conn->exec($sql); + } + + public function createConstraintSql($table, $name, $definition) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($this->getIndexName($name)); + $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name; + + if (isset($definition['primary']) && $definition['primary']) { + $query .= ' PRIMARY KEY'; + } elseif (isset($definition['unique']) && $definition['unique']) { + $query .= ' UNIQUE'; + } + + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $this->conn->quoteIdentifier($field); + } + $query .= ' ('. implode(', ', $fields) . ')'; + + return $query; + } + + public function createIndex($table, $name, array $definition) + { + return $this->conn->execute($this->createIndexSql($table, $name, $definition)); + } + + public function createIndexSql($table, $name, array $definition) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($name); + $type = ''; + + if (isset($definition['type'])) { + switch (strtolower($definition['type'])) { + case 'unique': + $type = strtoupper($definition['type']) . ' '; + break; + default: + throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); + } + } + + $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; + + $fields = array(); + foreach ($definition['fields'] as $field) { + $fields[] = $this->conn->quoteIdentifier($field); + } + $query .= ' (' . implode(', ', $fields) . ')'; + + return $query; + } + + public function createForeignKeySql($table, array $definition) + { + $table = $this->conn->quoteIdentifier($table); + + $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclaration($definition); + + return $query; + } + + public function createForeignKey($table, array $definition) + { + $sql = $this->createForeignKeySql($table, $definition); + + return $this->conn->execute($sql); + } + + public function alterTable($name, array $changes, $check = false) + { + $sql = $this->alterTableSql($name, $changes, $check); + + if (is_string($sql) && $sql) { + $this->conn->execute($sql); + } + } + + public function alterTableSql($name, array $changes, $check = false) + { + throw new IPF_ORM_Exception('Alter table not supported by this driver.'); + } + + public function getFieldDeclarationList(array $fields) + { + foreach ($fields as $fieldName => $field) { + $query = $this->getDeclaration($fieldName, $field); + + $queryFields[] = $query; + } + return implode(', ', $queryFields); + } + + public function getCheckDeclaration(array $definition) + { + $constraints = array(); + foreach ($definition as $field => $def) { + if (is_string($def)) { + $constraints[] = 'CHECK (' . $def . ')'; + } else { + if (isset($def['min'])) { + $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')'; + } + + if (isset($def['max'])) { + $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')'; + } + } + } + + return implode(', ', $constraints); + } + + public function getIndexDeclaration($name, array $definition) + { + $name = $this->conn->quoteIdentifier($name); + $type = ''; + + if (isset($definition['type'])) { + if (strtolower($definition['type']) == 'unique') { + $type = strtoupper($definition['type']) . ' '; + } else { + throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); + } + } + + if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) { + throw new IPF_ORM_Exception('No index columns given.'); + } + + $query = $type . 'INDEX ' . $name; + + $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; + + return $query; + } + + public function getIndexFieldDeclarationList(array $fields) + { + $ret = array(); + foreach ($fields as $field => $definition) { + if (is_array($definition)) { + $ret[] = $this->conn->quoteIdentifier($field); + } else { + $ret[] = $this->conn->quoteIdentifier($definition); + } + } + return implode(', ', $ret); + } + + public function getTemporaryTableQuery() + { + return 'TEMPORARY'; + } + + public function getForeignKeyDeclaration(array $definition) + { + $sql = $this->getForeignKeyBaseDeclaration($definition); + $sql .= $this->getAdvancedForeignKeyOptions($definition); + return $sql; + } + + public function getAdvancedForeignKeyOptions(array $definition) + { + $query = ''; + if ( ! empty($definition['onUpdate'])) { + $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']); + } + if ( ! empty($definition['onDelete'])) { + $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']); + } + return $query; + } + + public function getForeignKeyReferentialAction($action) + { + $upper = strtoupper($action); + switch ($upper) { + case 'CASCADE': + case 'SET NULL': + case 'NO ACTION': + case 'RESTRICT': + case 'SET DEFAULT': + return $upper; + break; + default: + throw new IPF_ORM_Exception('Unknown foreign key referential action \'' . $upper . '\' given.'); + } + } + + public function getForeignKeyBaseDeclaration(array $definition) + { + $sql = ''; + if (isset($definition['name'])) { + $sql .= ' CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' '; + } + $sql .= 'FOREIGN KEY ('; + + if ( ! isset($definition['local'])) { + throw new IPF_ORM_Exception('Local reference field missing from definition.'); + } + if ( ! isset($definition['foreign'])) { + throw new IPF_ORM_Exception('Foreign reference field missing from definition.'); + } + if ( ! isset($definition['foreignTable'])) { + throw new IPF_ORM_Exception('Foreign reference table missing from definition.'); + } + + if ( ! is_array($definition['local'])) { + $definition['local'] = array($definition['local']); + } + if ( ! is_array($definition['foreign'])) { + $definition['foreign'] = array($definition['foreign']); + } + + $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local'])) + . ') REFERENCES ' + . $this->conn->quoteIdentifier($definition['foreignTable']) . '(' + . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')'; + + return $sql; + } + + public function exportSortedClassesSql($classes, $groupByConnection = true) + { + $connections = array(); + foreach ($classes as $class) { + $connection = IPF_ORM_Manager::connection(); + $connectionName = $connection->getName(); + + if ( ! isset($connections[$connectionName])) { + $connections[$connectionName] = array( + 'create_tables' => array(), + 'create_indexes' => array(), + 'alters' => array() + ); + } + + $sql = $this->exportClassesSql($class); + + // Build array of all the creates + // We need these to happen first + foreach ($sql as $key => $query) { + // If create table statement + if (substr($query, 0, strlen('CREATE TABLE')) == 'CREATE TABLE') { + $connections[$connectionName]['create_tables'][] = $query; + + unset($sql[$key]); + continue; + } + + // If create index statement + if (preg_grep("/CREATE .* INDEX/", array($query))) { + $connections[$connectionName]['create_indexes'][] = $query; + + unset($sql[$key]); + continue; + } + + // If alter table statement + if (substr($query, 0, strlen('ALTER TABLE')) == 'ALTER TABLE') { + $connections[$connectionName]['alters'][] = $query; + + unset($sql[$key]); + continue; + } + } + } + + // Loop over all the sql again to merge everything together so it is in the correct order + $build = array(); + foreach ($connections as $connectionName => $sql) { + $build[$connectionName] = array_merge($sql['create_tables'], $sql['create_indexes'], $sql['alters']); + } + + if ( ! $groupByConnection) { + $new = array(); + foreach($build as $connectionname => $sql) { + $new = array_merge($new, $sql); + } + $build = $new; + } + return $build; + } + + public function exportClasses(array $classes) + { + $queries = $this->exportSortedClassesSql($classes); + + foreach ($queries as $connectionName => $sql) { + $connection = IPF_ORM_Manager::connection(); + + $connection->beginTransaction(); + + foreach ($sql as $query) { + try { + $connection->exec($query); + } catch (IPF_ORM_Exception $e) { + // we only want to silence table already exists errors + if ($e->getPortableCode() !== IPF_ORM::ERR_ALREADY_EXISTS) { + $connection->rollback(); + throw new IPF_ORM_Exception($e->getMessage() . '. Failing Query: ' . $query); + } + } + } + $connection->commit(); + } + } + + public function exportClassesSql($name) + { + $sql = array(); + + $table = IPF_ORM::getTable($name); + + $data = $this->getExportableFormat($table); + + $query = $this->createTableSql($data['tableName'], $data['columns'], $data['options']); + + if (is_array($query)) { + $sql = array_merge($sql, $query); + } else { + $sql[] = $query; + } + + $sql = array_unique($sql); + + rsort($sql); + + return $sql; + } + + private function getExportableFormat($table) + { + $columns = array(); + $primary = array(); + + foreach ($table->getColumns() as $name => $definition) { + + if (isset($definition['owner'])) { + continue; + } + + switch ($definition['type']) { + case 'enum': + if (isset($definition['default'])) { + $definition['default'] = $table->enumIndex($name, $definition['default']); + } + break; + case 'boolean': + if (isset($definition['default'])) { + $definition['default'] = $table->getConnection()->convertBooleans($definition['default']); + } + break; + } + $columns[$name] = $definition; + + if (isset($definition['primary']) && $definition['primary']) { + $primary[] = $name; + } + } + + $options['foreignKeys'] = $table->getOption('foreignKeys', array()); + + if ($table->getAttribute(IPF_ORM::ATTR_EXPORT) & IPF_ORM::EXPORT_CONSTRAINTS) { + $constraints = array(); + + $emptyIntegrity = array('onUpdate' => null, + 'onDelete' => null); + + foreach ($table->getRelations() as $name => $relation) { + $fk = $relation->toArray(); + $fk['foreignTable'] = $relation->getTable()->getTableName(); + + if ($relation->getTable() === $table && in_array($relation->getLocal(), $primary)) { + if ($relation->hasConstraint()) { + throw new IPF_ORM_Exception("Badly constructed integrity constraints."); + } + continue; + } + + $integrity = array('onUpdate' => $fk['onUpdate'], + 'onDelete' => $fk['onDelete']); + + if ($relation instanceof IPF_ORM_Relation_LocalKey) { + $def = array('local' => $relation->getLocal(), + 'foreign' => $relation->getForeign(), + 'foreignTable' => $relation->getTable()->getTableName()); + + if (($key = array_search($def, $options['foreignKeys'])) === false) { + $options['foreignKeys'][] = $def; + $constraints[] = $integrity; + } else { + if ($integrity !== $emptyIntegrity) { + $constraints[$key] = $integrity; + } + } + } + } + + foreach ($constraints as $k => $def) { + $options['foreignKeys'][$k] = array_merge($options['foreignKeys'][$k], $def); + } + } + + $options['primary'] = $primary; + + return array('tableName' => $table->getOption('tableName'), + 'columns' => $columns, + 'options' => array_merge($table->getOptions(), $options)); + } +} + diff --git a/src/orm/export/mysql.php b/src/orm/export/mysql.php new file mode 100644 index 0000000..1dbe90a --- /dev/null +++ b/src/orm/export/mysql.php @@ -0,0 +1,493 @@ +getFieldDeclarationList($fields); + + // build indexes for all foreign key fields (needed in MySQL!!) + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $fk) { + $local = $fk['local']; + $found = false; + if (isset($options['indexes'])) { + foreach ($options['indexes'] as $definition) { + if (is_string($definition['fields'])) { + // Check if index already exists on the column + $found = ($local == $definition['fields']); + } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) { + // Index already exists on the column + $found = true; + } + } + } + if (isset($options['primary']) && !empty($options['primary']) && + in_array($local, $options['primary'])) { + // field is part of the PK and therefore already indexed + $found = true; + } + + if ( ! $found) { + if (is_array($local)) { + foreach($local as $localidx) { + $options['indexes'][$localidx] = array('fields' => array($localidx => array())); + } + } else { + $options['indexes'][$local] = array('fields' => array($local => array())); + } + } + } + } + + // add all indexes + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition); + } + } + + // attach all primary keys + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_values($options['primary']); + $keyColumns = array_map(array($this->conn, 'quoteIdentifier'), $keyColumns); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name) . ' (' . $queryFields . ')'; + + $optionStrings = array(); + + if (isset($options['comment'])) { + $optionStrings['comment'] = 'COMMENT = ' . $this->conn->quote($options['comment'], 'text'); + } + if (isset($options['charset'])) { + $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset']; + } + if (isset($options['collate'])) { + $optionStrings['collate'] = 'COLLATE ' . $options['collate']; + } + + $type = false; + + // get the type of the table + if (isset($options['type'])) { + $type = $options['type']; + } else { + $type = $this->conn->getAttribute(IPF_ORM::ATTR_DEFAULT_TABLE_TYPE); + } + + if ($type) { + $optionStrings[] = 'ENGINE = ' . $type; + } + + if ( ! empty($optionStrings)) { + $query.= ' '.implode(' ', $optionStrings); + } + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + + foreach ((array) $options['foreignKeys'] as $k => $definition) { + if (is_array($definition)) { + $sql[] = $this->createForeignKeySql($name, $definition); + } + } + } + return $sql; + } + + public function getDeclaration($name, array $field) + { + $declaration = $this->conn->quoteIdentifier($name) . ' '; + + if (!isset($field['type'])) + throw new IPF_ORM_Exception('Missing column type.'); + + switch ($field['type']) { + case 'char': + $length = ( ! empty($field['length'])) ? $field['length'] : false; + + $declaration .= $length ? 'CHAR('.$length.')' : 'CHAR(255)'; + break; + case 'varchar': + case 'array': + case 'object': + case 'string': + case 'gzip': + if (!isset($field['length'])) { + if (array_key_exists('default', $field)) { + $field['length'] = $this->conn->varchar_max_length; + } else { + $field['length'] = false; + } + } + + $length = ($field['length'] <= $this->conn->varchar_max_length) ? $field['length'] : false; + $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; + + $declaration .= $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); + break; + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYTEXT'; + } elseif ($length <= 65532) { + return 'TEXT'; + } elseif ($length <= 16777215) { + return 'MEDIUMTEXT'; + } + } + $declaration .= 'LONGTEXT'; + break; + case 'blob': + if ( ! empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYBLOB'; + } elseif ($length <= 65532) { + return 'BLOB'; + } elseif ($length <= 16777215) { + return 'MEDIUMBLOB'; + } + } + $declaration .= 'LONGBLOB'; + break; + case 'enum': + if ($this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { + $values = array(); + foreach ($field['values'] as $value) { + $values[] = $this->conn->quote($value, 'varchar'); + } + $declaration .= 'ENUM('.implode(', ', $values).')'; + break; + } + // fall back to integer + case 'integer': + case 'int': + $type = 'INT'; + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 1) { + $type = 'TINYINT'; + } elseif ($length == 2) { + $type = 'SMALLINT'; + } elseif ($length == 3) { + $type = 'MEDIUMINT'; + } elseif ($length == 4) { + $type = 'INT'; + } elseif ($length > 4) { + $type = 'BIGINT'; + } + } + $declaration .= $type; + if (isset($field['unsigned']) && $field['unsigned']) + $declaration .= ' UNSIGNED'; + break; + case 'boolean': + $declaration .= 'TINYINT(1)'; + break; + case 'date': + $declaration .= 'DATE'; + break; + case 'time': + $declaration .= 'TIME'; + break; + case 'datetime': + $declaration .= 'DATETIME'; + break; + case 'timestamp': + $declaration .= 'TIMESTAMP'; + break; + case 'float': + case 'double': + $declaration .= 'DOUBLE'; + break; + case 'decimal': + $scale = !empty($field['scale']) ? $field['scale'] : $this->conn->getAttribute(IPF_ORM::ATTR_DECIMAL_PLACES); + if (!empty($field['length'])) { + $length = $field['length']; + if (is_array($length)) { + list($length, $scale) = $length; + } + } else { + $length = 18; + } + $declaration .= 'DECIMAL('.$length.','.$scale.')'; + break; + case 'bit': + $declaration .= 'BIT'; + break; + default: + throw new IPF_ORM_Exception('Unknown field type \'' . $field['type'] . '\'.'); + } + + if (isset($field['charset']) && $field['charset']) + $declaration .= ' CHARACTER SET ' . $field['charset']; + + if (isset($field['collate']) && $field['collate']) + $declaration .= ' COLLATE ' . $field['collate']; + + if (isset($field['notnull']) && $field['notnull']) + $declaration .= ' NOT NULL'; + + if (!empty($field['autoincrement'])) { + $declaration .= ' AUTO_INCREMENT'; + } else { + $declaration .= $this->getDefaultFieldDeclaration($field); + + if (isset($field['unique']) && $field['unique']) + $declaration .= ' UNIQUE'; + } + + if (isset($field['comment']) && $field['comment']) + $declaration .= ' COMMENT ' . $this->conn->quote($field['comment'], 'varchar'); + + return $declaration; + } + + private function getDefaultFieldDeclaration($field) + { + $default = ''; + if (isset($field['default']) && (!isset($field['length']) || $field['length'] <= 255)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : $this->valid_default_values[$field['type']]; + } + + if ($field['default'] === '' && ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EMPTY_TO_NULL)) + $field['default'] = null; + + if (is_null($field['default'])) { + $default = ' DEFAULT NULL'; + } else { + if ($field['type'] === 'boolean') { + $fieldType = 'boolean'; + $field['default'] = $this->conn->convertBooleans($field['default']); + } elseif ($field['type'] == 'enum' && $this->conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { + $fieldType = 'varchar'; + } else { + $fieldType = $field['type']; + } + $default = ' DEFAULT ' . $this->conn->quote($field['default'], $fieldType); + } + } + return $default; + } + + public function alterTableSql($name, array $changes, $check = false) + { + if ( ! $name) { + throw new IPF_ORM_Exception('no valid table name specified'); + } + foreach ($changes as $changeName => $change) { + switch ($changeName) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + throw new IPF_ORM_Exception('change type "' . $changeName . '" not yet supported'); + } + } + + if ($check) { + return true; + } + + $query = ''; + if ( ! empty($changes['name'])) { + $change_name = $this->conn->quoteIdentifier($changes['name']); + $query .= 'RENAME TO ' . $change_name; + } + + if ( ! empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $fieldName => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $this->getDeclaration($fieldName, $field); + } + } + + if ( ! empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $fieldName => $field) { + if ($query) { + $query .= ', '; + } + $fieldName = $this->conn->quoteIdentifier($fieldName); + $query .= 'DROP ' . $fieldName; + } + } + + $rename = array(); + if ( ! empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $fieldName => $field) { + $rename[$field['name']] = $fieldName; + } + } + + if ( ! empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $fieldName => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$fieldName])) { + $oldFieldName = $rename[$fieldName]; + unset($rename[$fieldName]); + } else { + $oldFieldName = $fieldName; + } + $oldFieldName = $this->conn->quoteIdentifier($oldFieldName); + $query .= 'CHANGE ' . $oldFieldName . ' ' + . $this->getDeclaration($fieldName, $field['definition']); + } + } + + if ( ! empty($rename) && is_array($rename)) { + foreach ($rename as $renameName => $renamedField) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamedField]; + $renamedField = $this->conn->quoteIdentifier($renamedField); + $query .= 'CHANGE ' . $renamedField . ' ' + . $this->getDeclaration($field['name'], $field['definition']); + } + } + + if ( ! $query) { + return false; + } + + $name = $this->conn->quoteIdentifier($name); + + return 'ALTER TABLE ' . $name . ' ' . $query; + } + + public function createIndexSql($table, $name, array $definition) + { + $name = $this->conn->quoteIdentifier($this->getIndexName($name)); + $type = ''; + if (isset($definition['type'])) { + switch (strtolower($definition['type'])) { + case 'fulltext': + case 'unique': + $type = strtoupper($definition['type']) . ' '; + break; + default: + throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); + } + } + $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; + $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; + + return $query; + } + + public function getIndexDeclaration($name, array $definition) + { + $name = $this->getIndexName($name); + $type = ''; + if (isset($definition['type'])) { + switch (strtolower($definition['type'])) { + case 'fulltext': + case 'unique': + $type = strtoupper($definition['type']) . ' '; + break; + default: + throw new IPF_ORM_Exception('Unknown index type ' . $definition['type']); + } + } + + if ( ! isset($definition['fields'])) { + throw new IPF_ORM_Exception('No index columns given.'); + } + if ( ! is_array($definition['fields'])) { + $definition['fields'] = array($definition['fields']); + } + + $query = $type . 'INDEX ' . $this->conn->quoteIdentifier($name); + + $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; + + return $query; + } + + public function getIndexFieldDeclarationList(array $fields) + { + $declFields = array(); + + foreach ($fields as $fieldName => $field) { + $fieldString = $this->conn->quoteIdentifier($fieldName); + + if (is_array($field)) { + if (isset($field['length'])) { + $fieldString .= '(' . $field['length'] . ')'; + } + + if (isset($field['sorting'])) { + $sort = strtoupper($field['sorting']); + switch ($sort) { + case 'ASC': + case 'DESC': + $fieldString .= ' ' . $sort; + break; + default: + throw new IPF_ORM_Exception('Unknown index sorting option given.'); + } + } + } else { + $fieldString = $this->conn->quoteIdentifier($field); + } + $declFields[] = $fieldString; + } + return implode(', ', $declFields); + } + + public function getAdvancedForeignKeyOptions(array $definition) + { + $query = ''; + if ( ! empty($definition['match'])) { + $query .= ' MATCH ' . $definition['match']; + } + if ( ! empty($definition['onUpdate'])) { + $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']); + } + if ( ! empty($definition['onDelete'])) { + $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']); + } + return $query; + } + + public function dropIndexSql($table, $name) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($this->getIndexName($name)); + return 'DROP INDEX ' . $name . ' ON ' . $table; + } + + public function dropTableSql($table) + { + $table = $this->conn->quoteIdentifier($table); + return 'DROP TABLE ' . $table; + } + + public function dropForeignKey($table, $name) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($name); + + return $this->conn->exec('ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $name); + } +} + diff --git a/src/orm/expression.php b/src/orm/expression.php new file mode 100644 index 0000000..a866412 --- /dev/null +++ b/src/orm/expression.php @@ -0,0 +1,72 @@ +_tokenizer = new IPF_ORM_Query_Tokenizer(); + $this->setExpression($expr); + if ($conn !== null) { + $this->_conn = $conn; + } + } + + public function getConnection() + { + if ( ! isset($this->_conn)) { + return IPF_ORM_Manager::connection(); + } + + return $this->_conn; + } + + public function setExpression($clause) + { + $this->_expression = $this->parseClause($clause); + } + + public function parseExpression($expr) + { + $pos = strpos($expr, '('); + $quoted = (substr($expr, 0, 1) === "'" && substr($expr, -1) === "'"); + if ($pos === false || $quoted) { + return $expr; + } + + // get the name of the function + $name = substr($expr, 0, $pos); + $argStr = substr($expr, ($pos + 1), -1); + + // parse args + foreach ($this->_tokenizer->bracketExplode($argStr, ',') as $arg) { + $args[] = $this->parseClause($arg); + } + + return call_user_func_array(array($this->getConnection()->expression, $name), $args); + } + + public function parseClause($clause) + { + $e = $this->_tokenizer->bracketExplode($clause, ' '); + + foreach ($e as $k => $expr) { + $e[$k] = $this->parseExpression($expr); + } + + return implode(' ', $e); + } + + public function getSql() + { + return $this->_expression; + } + + public function __toString() + { + return $this->getSql(); + } +} diff --git a/src/orm/expression/driver.php b/src/orm/expression/driver.php new file mode 100644 index 0000000..5c7baa2 --- /dev/null +++ b/src/orm/expression/driver.php @@ -0,0 +1,288 @@ +getIdentifier($column); + return 'AVG(' . $column . ')'; + } + + public function count($column) + { + $column = $this->getIdentifier($column); + return 'COUNT(' . $column . ')'; + } + + public function max($column) + { + $column = $this->getIdentifier($column); + return 'MAX(' . $column . ')'; + } + + public function min($column) + { + $column = $this->getIdentifier($column); + return 'MIN(' . $column . ')'; + } + + public function sum($column) + { + $column = $this->getIdentifier($column); + return 'SUM(' . $column . ')'; + } + + public function md5($column) + { + $column = $this->getIdentifier($column); + return 'MD5(' . $column . ')'; + } + + public function length($column) + { + $column = $this->getIdentifier($column); + return 'LENGTH(' . $column . ')'; + } + + public function round($column, $decimals = 0) + { + $column = $this->getIdentifier($column); + + return 'ROUND(' . $column . ', ' . $decimals . ')'; + } + + public function mod($expression1, $expression2) + { + $expression1 = $this->getIdentifier($expression1); + $expression2 = $this->getIdentifier($expression2); + return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; + } + + public function trim($str) + { + return 'TRIM(' . $str . ')'; + } + + public function rtrim($str) + { + return 'RTRIM(' . $str . ')'; + } + + public function ltrim($str) + { + return 'LTRIM(' . $str . ')'; + } + + public function upper($str) + { + return 'UPPER(' . $str . ')'; + } + + public function lower($str) + { + return 'LOWER(' . $str . ')'; + } + + public function locate($str, $substr) + { + return 'LOCATE(' . $str . ', ' . $substr . ')'; + } + + public function now() + { + return 'NOW()'; + } + + public function coalesce($expression1, $expression2) + { + $expression1 = $this->getIdentifier($expression1); + $expression2 = $this->getIdentifier($expression2); + return 'COALESCE(' . $expression1 . ', ' . $expression2 . ')'; + } + + public function soundex($value) + { + throw new IPF_ORM_Exception('SQL soundex function not supported by this driver.'); + } + + public function substring($value, $from, $len = null) + { + $value = $this->getIdentifier($value); + if ($len === null) + return 'SUBSTRING(' . $value . ' FROM ' . $from . ')'; + else { + $len = $this->getIdentifier($len); + return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $len . ')'; + } + } + + public function concat() + { + $args = func_get_args(); + + return 'CONCAT(' . join(', ', (array) $args) . ')'; + } + + public function not($expression) + { + $expression = $this->getIdentifier($expression); + return 'NOT(' . $expression . ')'; + } + + private function basicMath($type, array $args) + { + $elements = $this->getIdentifiers($args); + if (count($elements) < 1) { + return ''; + } + if (count($elements) == 1) { + return $elements[0]; + } else { + return '(' . implode(' ' . $type . ' ', $elements) . ')'; + } + } + + public function add(array $args) + { + return $this->basicMath('+', $args); + } + + public function sub(array $args) + { + return $this->basicMath('-', $args ); + } + + public function mul(array $args) + { + return $this->basicMath('*', $args); + } + + public function div(array $args) + { + return $this->basicMath('/', $args); + } + + public function eq($value1, $value2) + { + $value1 = $this->getIdentifier($value1); + $value2 = $this->getIdentifier($value2); + return $value1 . ' = ' . $value2; + } + + public function neq($value1, $value2) + { + $value1 = $this->getIdentifier($value1); + $value2 = $this->getIdentifier($value2); + return $value1 . ' <> ' . $value2; + } + + public function gt($value1, $value2) + { + $value1 = $this->getIdentifier($value1); + $value2 = $this->getIdentifier($value2); + return $value1 . ' > ' . $value2; + } + + public function gte($value1, $value2) + { + $value1 = $this->getIdentifier($value1); + $value2 = $this->getIdentifier($value2); + return $value1 . ' >= ' . $value2; + } + + public function lt($value1, $value2) + { + $value1 = $this->getIdentifier($value1); + $value2 = $this->getIdentifier($value2); + return $value1 . ' < ' . $value2; + } + + public function lte($value1, $value2) + { + $value1 = $this->getIdentifier($value1); + $value2 = $this->getIdentifier($value2); + return $value1 . ' <= ' . $value2; + } + + public function in($column, $values) + { + if ( ! is_array($values)) { + $values = array($values); + } + $values = $this->getIdentifiers($values); + $column = $this->getIdentifier($column); + + if (count($values) == 0) { + throw new IPF_ORM_Exception('Values array for IN operator should not be empty.'); + } + return $column . ' IN (' . implode(', ', $values) . ')'; + } + + public function isNull($expression) + { + $expression = $this->getIdentifier($expression); + return $expression . ' IS NULL'; + } + + public function isNotNull($expression) + { + $expression = $this->getIdentifier($expression); + return $expression . ' IS NOT NULL'; + } + + public function between($expression, $value1, $value2) + { + $expression = $this->getIdentifier($expression); + $value1 = $this->getIdentifier($value1); + $value2 = $this->getIdentifier($value2); + return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2; + } + + public function guid() + { + throw new IPF_ORM_Exception('method not implemented'); + } + + public function acos($value) + { + return 'ACOS(' . $value . ')'; + } + + public function sin($value) + { + return 'SIN(' . $value . ')'; + } + + public function pi() + { + return 'PI()'; + } + + public function cos($value) + { + return 'COS(' . $value . ')'; + } + + public function __call($m, $a) + { + if ($this->conn->getAttribute(IPF_ORM::ATTR_PORTABILITY) & IPF_ORM::PORTABILITY_EXPR) { + throw new IPF_ORM_Exception('Unknown expression ' . $m); + } + return $m . '(' . implode(', ', $a) . ')'; + } +} + diff --git a/src/orm/expression/mysql.php b/src/orm/expression/mysql.php new file mode 100644 index 0000000..cc11fc6 --- /dev/null +++ b/src/orm/expression/mysql.php @@ -0,0 +1,43 @@ +getIdentifier($column); + return 'YEAR(' . $column . ')'; + } + + public function month($column) + { + $column = $this->getIdentifier($column); + return 'MONTH(' . $column . ')'; + } + + public function monthname($column) + { + $column = $this->getIdentifier($column); + return 'MONTHNAME(' . $column . ')'; + } + + public function day($column) + { + $column = $this->getIdentifier($column); + return 'DAY(' . $column . ')'; + } +} diff --git a/src/orm/hydrator.php b/src/orm/hydrator.php new file mode 100644 index 0000000..a4bb6bf --- /dev/null +++ b/src/orm/hydrator.php @@ -0,0 +1,271 @@ +_hydrationMode; + + $this->_tableAliases = $tableAliases; + + if ($hydrationMode == IPF_ORM::HYDRATE_NONE) { + return $stmt->fetchAll(PDO::FETCH_NUM); + } + + if ($hydrationMode == IPF_ORM::HYDRATE_ARRAY) { + $driver = new IPF_ORM_Hydrator_ArrayDriver(); + } else { + $driver = new IPF_ORM_Hydrator_RecordDriver(); + } + + // Used variables during hydration + reset($this->_queryComponents); + $rootAlias = key($this->_queryComponents); + $rootComponentName = $this->_queryComponents[$rootAlias]['table']->getComponentName(); + // if only one component is involved we can make our lives easier + $isSimpleQuery = count($this->_queryComponents) <= 1; + // Holds the resulting hydrated data structure + $result = array(); + // Lookup map to quickly discover/lookup existing records in the result + $identifierMap = array(); + // Holds for each component the last previously seen element in the result set + $prev = array(); + // holds the values of the identifier/primary key fields of components, + // separated by a pipe '|' and grouped by component alias (r, u, i, ... whatever) + // the $idTemplate is a prepared template. $id is set to a fresh template when + // starting to process a row. + $id = array(); + $idTemplate = array(); + + $result = $driver->getElementCollection($rootComponentName); + + if ($stmt === false || $stmt === 0) { + return $result; + } + + // Initialize + foreach ($this->_queryComponents as $dqlAlias => $data) { + $componentName = $data['table']->getComponentName(); + $identifierMap[$dqlAlias] = array(); + $prev[$dqlAlias] = null; + $idTemplate[$dqlAlias] = ''; + } + + $event = new IPF_ORM_Event(null, IPF_ORM_Event::HYDRATE, null); + + // Process result set + $cache = array(); + while ($data = $stmt->fetch(IPF_ORM::FETCH_ASSOC)) { + $id = $idTemplate; // initialize the id-memory + $nonemptyComponents = array(); + $rowData = $this->_gatherRowData($data, $cache, $id, $nonemptyComponents); + + // + // hydrate the data of the root component from the current row + // + $table = $this->_queryComponents[$rootAlias]['table']; + $componentName = $table->getComponentName(); + // Ticket #1115 (getInvoker() should return the component that has addEventListener) + $event->setInvoker($table); + $event->set('data', $rowData[$rootAlias]); + $table->notifyRecordListeners('preHydrate', $event); + + $index = false; + + // Check for an existing element + if ($isSimpleQuery || ! isset($identifierMap[$rootAlias][$id[$rootAlias]])) { + $element = $driver->getElement($rowData[$rootAlias], $componentName); + $event->set('data', $element); + $table->notifyRecordListeners('postHydrate', $event); + + // do we need to index by a custom field? + if ($field = $this->_getCustomIndexField($rootAlias)) { + if (isset($result[$field])) { + throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping."); + } else if ( ! isset($element[$field])) { + throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key."); + } + $result[$element[$field]] = $element; + } else { + $result[] = $element; + } + + $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($result); + } else { + $index = $identifierMap[$rootAlias][$id[$rootAlias]]; + } + + $this->_setLastElement($prev, $result, $index, $rootAlias, false); + unset($rowData[$rootAlias]); + + // end hydrate data of the root component for the current row + + + // $prev[$rootAlias] now points to the last element in $result. + // now hydrate the rest of the data found in the current row, that belongs to other + // (related) components. + foreach ($rowData as $dqlAlias => $data) { + $index = false; + $map = $this->_queryComponents[$dqlAlias]; + $table = $map['table']; + $componentName = $table->getComponentName(); + $event->set('data', $data); + $table->notifyRecordListeners('preHydrate', $event); + + $parent = $map['parent']; + $relation = $map['relation']; + $relationAlias = $map['relation']->getAlias(); + + $path = $parent . '.' . $dqlAlias; + + if ( ! isset($prev[$parent])) { + unset($prev[$dqlAlias]); // Ticket #1228 + continue; + } + + // check the type of the relation + if ( ! $relation->isOneToOne() && $driver->initRelated($prev[$parent], $relationAlias)) { + $oneToOne = false; + // append element + if (isset($nonemptyComponents[$dqlAlias])) { + $indexExists = isset($identifierMap[$path][$id[$parent]][$id[$dqlAlias]]); + $index = $indexExists ? $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false; + $indexIsValid = $index !== false ? isset($prev[$parent][$relationAlias][$index]) : false; + if ( ! $indexExists || ! $indexIsValid) { + $element = $driver->getElement($data, $componentName); + $event->set('data', $element); + $table->notifyRecordListeners('postHydrate', $event); + + if ($field = $this->_getCustomIndexField($dqlAlias)) { + if (isset($prev[$parent][$relationAlias][$element[$field]])) { + throw new IPF_ORM_Exception("Couldn't hydrate. Found non-unique key mapping."); + } else if ( ! isset($element[$field])) { + throw new IPF_ORM_Exception("Couldn't hydrate. Found a non-existent key."); + } + $prev[$parent][$relationAlias][$element[$field]] = $element; + } else { + $prev[$parent][$relationAlias][] = $element; + } + $identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = $driver->getLastKey($prev[$parent][$relationAlias]); + } + // register collection for later snapshots + $driver->registerCollection($prev[$parent][$relationAlias]); + } + } else { + // 1-1 relation + $oneToOne = true; + + if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($prev[$parent][$relationAlias])) { + $prev[$parent][$relationAlias] = $driver->getNullPointer(); + } else if ( ! isset($prev[$parent][$relationAlias])) { + $element = $driver->getElement($data, $componentName); + + // [FIX] Tickets #1205 and #1237 + $event->set('data', $element); + $table->notifyRecordListeners('postHydrate', $event); + + $prev[$parent][$relationAlias] = $element; + } + } + + $coll =& $prev[$parent][$relationAlias]; + $this->_setLastElement($prev, $coll, $index, $dqlAlias, $oneToOne); + } + } + + $stmt->closeCursor(); + $driver->flush(); + //$e = microtime(true); + //echo 'Hydration took: ' . ($e - $s) . ' for '.count($result).' records
'; + + return $result; + } + + protected function _setLastElement(&$prev, &$coll, $index, $dqlAlias, $oneToOne) + { + if (IPF_ORM_Null::isNull($coll) || $coll === null) { + unset($prev[$dqlAlias]); // Ticket #1228 + return; + } + + if ($index !== false) { + // Link element at $index to previous element for the component + // identified by the DQL alias $alias + $prev[$dqlAlias] =& $coll[$index]; + return; + } + + if (is_array($coll) && $coll) { + if ($oneToOne) { + $prev[$dqlAlias] =& $coll; + } else { + end($coll); + $prev[$dqlAlias] =& $coll[key($coll)]; + } + } else if (count($coll) > 0) { + $prev[$dqlAlias] = $coll->getLast(); + } + } + + protected function _gatherRowData(&$data, &$cache, &$id, &$nonemptyComponents) + { + $rowData = array(); + + foreach ($data as $key => $value) { + // Parse each column name only once. Cache the results. + if ( ! isset($cache[$key])) { + // check ignored names. fastest solution for now. if we get more we'll start + // to introduce a list. + if ($key == 'IPF_ORM_ROWNUM') continue; + $e = explode('__', $key); + $last = strtolower(array_pop($e)); + $cache[$key]['dqlAlias'] = $this->_tableAliases[strtolower(implode('__', $e))]; + $table = $this->_queryComponents[$cache[$key]['dqlAlias']]['table']; + $fieldName = $table->getFieldName($last); + $cache[$key]['fieldName'] = $fieldName; + if ($table->isIdentifier($fieldName)) { + $cache[$key]['isIdentifier'] = true; + } else { + $cache[$key]['isIdentifier'] = false; + } + $type = $table->getTypeOfColumn($last); + if ($type == 'integer' || $type == 'string') { + $cache[$key]['isSimpleType'] = true; + } else { + $cache[$key]['type'] = $type; + $cache[$key]['isSimpleType'] = false; + } + } + + $map = $this->_queryComponents[$cache[$key]['dqlAlias']]; + $table = $map['table']; + $dqlAlias = $cache[$key]['dqlAlias']; + $fieldName = $cache[$key]['fieldName']; + if (isset($this->_queryComponents[$dqlAlias]['agg'][$fieldName])) { + $fieldName = $this->_queryComponents[$dqlAlias]['agg'][$fieldName]; + } + + if ($cache[$key]['isIdentifier']) { + $id[$dqlAlias] .= '|' . $value; + } + + if ($cache[$key]['isSimpleType']) { + $rowData[$dqlAlias][$fieldName] = $value; + } else { + $rowData[$dqlAlias][$fieldName] = $table->prepareValue( + $fieldName, $value, $cache[$key]['type']); + } + + if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { + $nonemptyComponents[$dqlAlias] = true; + } + } + + return $rowData; + } + + protected function _getCustomIndexField($alias) + { + return isset($this->_queryComponents[$alias]['map']) ? $this->_queryComponents[$alias]['map'] : null; + } +} diff --git a/src/orm/hydrator/abstract.php b/src/orm/hydrator/abstract.php new file mode 100644 index 0000000..497d2ca --- /dev/null +++ b/src/orm/hydrator/abstract.php @@ -0,0 +1,32 @@ +_hydrationMode = $hydrationMode; + } + + public function getHydrationMode() + { + return $this->_hydrationMode; + } + + public function setQueryComponents(array $queryComponents) + { + $this->_queryComponents = $queryComponents; + } + + public function getQueryComponents() + { + return $this->_queryComponents; + } + + abstract public function hydrateResultSet($stmt, $tableAliases); +} diff --git a/src/orm/hydrator/arraydriver.php b/src/orm/hydrator/arraydriver.php new file mode 100644 index 0000000..6018d62 --- /dev/null +++ b/src/orm/hydrator/arraydriver.php @@ -0,0 +1,40 @@ +_collections[] = $coll; + + return $coll; + } + + public function getLastKey($coll) + { + $coll->end(); + + return $coll->key(); + } + + public function initRelated(IPF_ORM_Record $record, $name) + { + if ( ! isset($this->_initializedRelations[$record->getOid()][$name])) { + $relation = $record->getTable()->getRelation($name); + $coll = new IPF_ORM_Collection($relation->getTable()->getComponentName()); + $coll->setReference($record, $relation); + $record[$name] = $coll; + $this->_initializedRelations[$record->getOid()][$name] = true; + } + return true; + } + + public function registerCollection(IPF_ORM_Collection $coll) + { + $this->_collections[] = $coll; + } + + public function getNullPointer() + { + return IPF_ORM_Null::getInstance(); + } + + public function getElement(array $data, $component) + { + $component = $this->_getClassNameToReturn($data, $component); + if ( ! isset($this->_tables[$component])) { + $this->_tables[$component] = IPF_ORM::getTable($component); + $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false); + } + + $this->_tables[$component]->setData($data); + $record = $this->_tables[$component]->getRecord(); + + return $record; + } + + public function flush() + { + // take snapshots from all initialized collections + foreach ($this->_collections as $key => $coll) { + $coll->takeSnapshot(); + } + foreach ($this->_tables as $table) { + $table->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, true); + } + $this->_initializedRelations = null; + $this->_collections = null; + $this->_tables = null; + } + + protected function _getClassNameToReturn(array &$data, $component) + { + if ( ! isset($this->_tables[$component])) { + $this->_tables[$component] = IPF_ORM::getTable($component); + $this->_tables[$component]->setAttribute(IPF_ORM::ATTR_LOAD_REFERENCES, false); + } + + if ( ! ($subclasses = $this->_tables[$component]->getOption('subclasses'))) { + return $component; + } + + foreach ($subclasses as $subclass) { + $table = IPF_ORM::getTable($subclass); + $inheritanceMap = $table->getOption('inheritanceMap'); + list($key, $value) = each($inheritanceMap); + if ( ! isset($data[$key]) || $data[$key] != $value) { + continue; + } else { + return $table->getComponentName(); + } + } + return $component; + } +} diff --git a/src/orm/import/builder.php b/src/orm/import/builder.php new file mode 100644 index 0000000..9aaae70 --- /dev/null +++ b/src/orm/import/builder.php @@ -0,0 +1,388 @@ +setTableName(\''. $definition['tableName'].'\');'; + + if (isset($definition['columns']) && is_array($definition['columns']) && !empty($definition['columns'])) + $ret[] = $this->buildColumns($definition['columns']); + + if (isset($definition['ordering']) && is_array($definition['ordering']) && !empty($definition['ordering'])) + $ret[] = ' $table->setOrdering('.self::varExportList($definition['ordering']).');'; + + if (isset($definition['indexes']) && is_array($definition['indexes']) && !empty($definition['indexes'])) + foreach ($definition['indexes'] as $indexName => $definitions) + $ret[] = " \$table->addIndex('" . $indexName . "', " . self::varExport($definitions) . ');'; + + if (isset($definition['attributes']) && is_array($definition['attributes']) && !empty($definition['attributes'])) + $ret[] = $this->buildAttributes($definition['attributes']); + + if (isset($definition['options']) && is_array($definition['options']) && !empty($definition['options'])) + $ret[] = $this->buildOptions($definition['options']); + + if (isset($definition['inheritance']['subclasses']) && !empty($definition['inheritance']['subclasses'])) { + $ret[] = " ".'$table->setSubClasses('. self::varExport($definition['inheritance']['subclasses']).');'; + } + + $ret[] = ' }'; + + return implode(PHP_EOL, $ret); + } + + private function buildSetUp(array $definition) + { + $ret = array(); + + if (isset($definition['relations']) && is_array($definition['relations']) && !empty($definition['relations'])) { + foreach ($definition['relations'] as $name => $relation) { + $class = isset($relation['class']) ? $relation['class'] : $name; + $alias = (isset($relation['alias']) && $relation['alias'] !== $relation['class']) ? $relation['alias'] : ''; + + if (!isset($relation['type'])) + $relation['type'] = IPF_ORM_Relation::ONE; + + if ($relation['type'] === IPF_ORM_Relation::ONE || + $relation['type'] === IPF_ORM_Relation::ONE_COMPOSITE) { + $r = " \$table->hasOne('$class', '$alias'"; + } else { + $r = " \$table->hasMany('$class', '$alias'"; + } + + $a = array(); + + if (isset($relation['refClass'])) { + $a[] = '\'refClass\' => ' . self::varExport($relation['refClass']); + } + + if (isset($relation['deferred']) && $relation['deferred']) { + $a[] = '\'default\' => ' . self::varExport($relation['deferred']); + } + + if (isset($relation['local']) && $relation['local']) { + $a[] = '\'local\' => ' . self::varExport($relation['local']); + } + + if (isset($relation['foreign']) && $relation['foreign']) { + $a[] = '\'foreign\' => ' . self::varExport($relation['foreign']); + } + + if (isset($relation['onDelete']) && $relation['onDelete']) { + $a[] = '\'onDelete\' => ' . self::varExport($relation['onDelete']); + } + + if (isset($relation['onUpdate']) && $relation['onUpdate']) { + $a[] = '\'onUpdate\' => ' . self::varExport($relation['onUpdate']); + } + + if (isset($relation['equal']) && $relation['equal']) { + $a[] = '\'equal\' => ' . self::varExport($relation['equal']); + } + + if (isset($relation['owningSide']) && $relation['owningSide']) { + $a[] = '\'owningSide\' => ' . self::varExport($relation['owningSide']); + } + + if (isset($relation['exclude']) && $relation['exclude']) { + $a[] = '\'exclude\' => ' . self::varExport($relation['exclude']); + } + + if (!empty($a)) + $r .= ', array(' . implode(', ', $a) . ')'; + + $ret[] = $r.');'; + } + } + + if (isset($definition['templates']) && is_array($definition['templates']) && !empty($definition['templates'])) { + $this->buildTemplates($definition['templates'], $ret); + } + + if (isset($definition['actAs']) && is_array($definition['actAs']) && !empty($definition['actAs'])) { + $this->buildActAs($definition['actAs'], $ret); + } + + if (isset($definition['listeners']) && is_array($definition['listeners']) && !empty($definition['listeners'])) { + foreach ($definition['listeners'] as $listener) { + $ret[] = " \$table->listeners['$listener'] = new $listener;"; + } + } + + // If the body of the function has contents and we are using inheritance + // then we need call the parent::setUp() before the body of the function + // Class table inheritance is the only one we shouldn't call parent::setUp() for + if (count($ret) && isset($definition['inheritance']['type']) && $definition['inheritance']['type'] != 'class_table') { + array_unshift($ret, ' parent::setUp($table);'); + } + + // If we have some code for the function then lets define it and return it + if (count($ret)) { + array_unshift($ret, + ' public static function setUp(IPF_ORM_Table $table)', + ' {' + ); + $ret[] = ' }'; + } + return $ret; + } + + private function buildColumns(array $columns) + { + $result = array(); + foreach ($columns as $name => $column) { + $columnName = isset($column['name']) ? $column['name']:$name; + $build = " ".'$table->setColumn(\'' . $columnName . '\', \'' . $column['type'] . '\''; + + if ($column['length']) { + if (is_numeric($column['length'])) + $build .= ', ' . $column['length']; + else + $build .= ', array(' . $column['length'] . ')'; + } else { + $build .= ', null'; + } + + $options = $column; + + // Remove name, alltypes, ntype. They are not needed in options array + unset($options['name']); + unset($options['alltypes']); + unset($options['ntype']); + + // Remove notnull => true if the column is primary + // Primary columns are implied to be notnull in IPF_ORM + if (isset($options['primary']) && $options['primary'] == true && (isset($options['notnull']) && $options['notnull'] == true)) { + unset($options['notnull']); + } + + // Remove default if the value is 0 and the column is a primary key + // IPF_ORM defaults to 0 if it is a primary key + if (isset($options['primary']) && $options['primary'] == true && (isset($options['default']) && $options['default'] == 0)) { + unset($options['default']); + } + + // These can be removed if they are empty. They all default to a false/0/null value anyways + $remove = array('fixed', 'primary', 'notnull', 'autoincrement', 'unsigned'); + foreach ($remove as $key) { + if (isset($options[$key]) && empty($options[$key])) { + unset($options[$key]); + } + } + + // Remove null and empty array values + foreach ($options as $key => $value) { + if (is_null($value) || (is_array($value) && empty($value))) { + unset($options[$key]); + } + } + + if (is_array($options) && !empty($options)) { + $build .= ', ' . self::varExport($options); + } + + $build .= ');'; + + $result[] = $build; + } + + return implode(PHP_EOL, $result); + } + + private function buildTemplates(array $templates, array &$build) + { + foreach ($templates as $name => $options) { + if (is_array($options) && !empty($options)) { + $build[] = " \$table->addTemplate('$name', " . self::varExport($options) . ");"; + } elseif (isset($templates[0])) { + $build[] = " \$table->addTemplate('$options');"; + } else { + $build[] = " \$table->addTemplate('$name');"; + } + } + } + + private function buildActAs($actAs, array &$build) + { + // rewrite special case of actAs: [Behavior] which gave [0] => Behavior + if (is_array($actAs) && isset($actAs[0]) && !is_array($actAs[0])) { + $actAs = array_flip($actAs); + } + + // rewrite special case of actAs: Behavior + if (!is_array($actAs)) + $actAs = array($actAs => ''); + + foreach ($actAs as $template => $options) { + // find class matching $name + if (class_exists('IPF_ORM_Template_'.$template, true)) + $classname = 'IPF_ORM_Template_'.$template; + else + $classname = $template; + + if (is_array($options)) + $options = self::varExport($options); + else + $options = ''; + + $build[] = " \$table->addTemplate(new $classname($options));"; + } + } + + private function buildAttributes(array $attributes) + { + $build = PHP_EOL; + foreach ($attributes as $key => $value) { + + if (is_bool($value)) { + $values = $value ? 'true':'false'; + } else { + if (!is_array($value)) + $value = array($value); + + $values = ''; + foreach ($value as $attr) { + $values .= "IPF_ORM::" . strtoupper($key) . "_" . strtoupper($attr) . ' ^ '; + } + + // Trim last ^ + $values = substr($values, 0, strlen($values) - 3); + } + + $build .= " \$table->setAttribute(IPF_ORM::ATTR_" . strtoupper($key) . ", " . $values . ");" . PHP_EOL; + } + + return $build; + } + + private function buildOptions(array $options) + { + $build = ''; + foreach ($options as $name => $value) { + $build .= " \$table->setOption('$name', " . self::varExport($value) . ");" . PHP_EOL; + } + return $build; + } + + public function buildRecord(array $definition, $targetPath) + { + if (!isset($definition['className'])) + throw new IPF_ORM_Exception('Missing class name.'); + + $this->writeBaseDefinition($definition, $targetPath); + $this->writeModelDefinition($definition, $targetPath); + } + + private function writeBaseDefinition(array $definition, $targetPath) + { + $code = array( + '_baseClassPrefix.$definition['className'].' extends IPF_ORM_Record'; + $code[] = '{'; + $code[] = $this->buildTableDefinition($definition); + $code[] = ''; + $code = array_merge($code, $this->buildSetUp($definition)); + $code[] = ''; + $code = array_merge($code, $this->buildShortcuts($definition)); + $code[] = '}'; + + $fileName = $this->_baseClassPrefix . $definition['className'] . '.php'; + $writePath = $targetPath . DIRECTORY_SEPARATOR . $this->_baseClassesDirectory; + IPF_Utils::makeDirectories($writePath); + $writePath .= DIRECTORY_SEPARATOR . $fileName; + + if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false) + throw new IPF_ORM_Exception("Couldn't write file " . $writePath); + } + + private function buildShortcuts(array $definition) + { + return array( + ' public static function table()', + ' {', + ' return IPF_ORM::getTable(\''.$definition['className'].'\');', + ' }', + '', + ' public static function query($alias=\'\')', + ' {', + ' return IPF_ORM::getTable(\''.$definition['className'].'\')->createQuery($alias);', + ' }', + ); + } + + private function writeModelDefinition(array $definition, $targetPath) + { + $className = $definition['className']; + $adminClassName = $className.'Admin'; + + $writePath = $targetPath . DIRECTORY_SEPARATOR . $className . '.php'; + if (file_exists($writePath)) + return; + + $code = array( + '_baseClassPrefix, $className), + '{', + '}', + '', + '/*', + 'class '.$adminClassName.' extends IPF_Admin_Model', + '{', + '}', + '', + '// Add following line to your admin.php to enable admin interface for this model', + "new $adminClassName('$className'),", + '*/', + '', + ); + + IPF_Utils::makeDirectories($targetPath); + if (file_put_contents($writePath, implode(PHP_EOL, $code)) === false) + throw new IPF_ORM_Exception("Couldn't write file " . $writePath); + } +} + diff --git a/src/orm/import/schema.php b/src/orm/import/schema.php new file mode 100644 index 0000000..889e070 --- /dev/null +++ b/src/orm/import/schema.php @@ -0,0 +1,490 @@ + array( + 'abstract', + 'connection', + 'className', + 'tableName', + 'connection', + 'relations', + 'columns', + 'indexes', + 'attributes', + 'templates', + 'actAs', + 'options', + 'inheritance', + 'detect_relations', + 'listeners', + 'ordering', + ), + 'column' => array( + 'name', + 'format', + 'fixed', + 'primary', + 'autoincrement', + 'type', + 'length', + 'size', + 'default', + 'scale', + 'values', + 'comment', + 'protected', + 'zerofill', + 'owner', + 'exclude', + 'collate', + 'charset', + ), + 'relation' => array( + 'key', + 'class', + 'alias', + 'type', + 'refClass', + 'local', + 'exclude', + 'foreign', + 'foreignClass', + 'foreignAlias', + 'foreignType', + 'foreignExclude', + 'autoComplete', + 'onDelete', + 'onUpdate', + 'equal', + 'owningSide', + ), + 'inheritance' => array( + 'type', + 'extends', + 'keyField', + 'keyValue', + ), + ); + + protected $_validators = array(); + + public function getValidators() + { + if (empty($this->_validators)) { + $this->_validators = IPF_ORM_Utils::getValidators(); + } + return $this->_validators; + } + + public function importSchema($filename, $extraAllwedReferences) + { + $filename = (array)$filename; + $array = array(); + foreach ($filename as $fn) { + $definitions = $this->parseSchema($fn); + foreach ($definitions as &$definition) + $definition['input_file'] = $fn; + $array = array_merge($array, $definitions); + } + + $array = $this->_buildRelationships($array, $extraAllwedReferences); + $array = $this->_processInheritance($array); + return $array; + } + + private function parseSchema($schema) + { + $defaults = array('abstract' => false, + 'className' => null, + 'tableName' => null, + 'connection' => null, + 'relations' => array(), + 'indexes' => array(), + 'attributes' => array(), + 'templates' => array(), + 'actAs' => array(), + 'options' => array(), + 'inheritance' => array(), + 'detect_relations' => false); + + $array = Spyc::YAMLLoad($schema); + + // Go through the schema and look for global values so we can assign them to each table/class + $globals = array(); + $globalKeys = array('connection', + 'attributes', + 'templates', + 'actAs', + 'options', + 'inheritance', + 'detect_relations'); + + // Loop over and build up all the global values and remove them from the array + foreach ($array as $key => $value) { + if (in_array($key, $globalKeys)) { + unset($array[$key]); + $globals[$key] = $value; + } + } + + // Apply the globals to each table if it does not have a custom value set already + foreach ($array as $className => $table) { + foreach ($globals as $key => $value) { + if (!isset($array[$className][$key])) { + $array[$className][$key] = $value; + } + } + } + + $build = array(); + + foreach ($array as $className => $table) { + $this->_validateSchemaElement('root', array_keys($table), $className); + + $columns = array(); + + $className = isset($table['className']) ? (string) $table['className']:(string) $className; + + if (isset($table['inheritance']['keyField']) || isset($table['inheritance']['keyValue'])) { + $table['inheritance']['type'] = 'column_aggregation'; + } + + if (isset($table['tableName']) && $table['tableName']) { + $tableName = $table['tableName']; + } else { + if (isset($table['inheritance']['type']) && ($table['inheritance']['type'] == 'column_aggregation')) { + $tableName = null; + } else { + $tableName = IPF_ORM_Inflector::tableize($className); + } + } + + $connection = isset($table['connection']) ? $table['connection']:'current'; + + $columns = isset($table['columns']) ? $table['columns']:array(); + + if ( ! empty($columns)) { + foreach ($columns as $columnName => $field) { + + // Support short syntax: my_column: integer(4) + if ( ! is_array($field)) { + $original = $field; + $field = array(); + $field['type'] = $original; + } + + $colDesc = array(); + if (isset($field['name'])) { + $colDesc['name'] = $field['name']; + } else { + $colDesc['name'] = $columnName; + } + + $this->_validateSchemaElement('column', array_keys($field), $className . '->columns->' . $colDesc['name']); + + // Support short type(length) syntax: my_column: { type: integer(4) } + $e = explode('(', $field['type']); + if (isset($e[0]) && isset($e[1])) { + $colDesc['type'] = $e[0]; + $colDesc['length'] = substr($e[1], 0, strlen($e[1]) - 1); + } else { + $colDesc['type'] = isset($field['type']) ? (string) $field['type']:null; + $colDesc['length'] = isset($field['length']) ? (int) $field['length']:null; + $colDesc['length'] = isset($field['size']) ? (int) $field['size']:$colDesc['length']; + } + + $colDesc['fixed'] = isset($field['fixed']) ? (int) $field['fixed']:null; + $colDesc['primary'] = isset($field['primary']) ? (bool) (isset($field['primary']) && $field['primary']):null; + $colDesc['default'] = isset($field['default']) ? $field['default']:null; + $colDesc['autoincrement'] = isset($field['autoincrement']) ? (bool) (isset($field['autoincrement']) && $field['autoincrement']):null; + $colDesc['values'] = isset($field['values']) ? (array) $field['values']:null; + + // Include all the specified and valid validators in the colDesc + $validators = $this->getValidators(); + + foreach ($validators as $validator) { + if (isset($field[$validator])) { + $colDesc[$validator] = $field[$validator]; + } + } + + $columns[(string) $columnName] = $colDesc; + } + } + + // Apply the default values + foreach ($defaults as $key => $defaultValue) { + if (isset($table[$key]) && ! isset($build[$className][$key])) { + $build[$className][$key] = $table[$key]; + } else { + $build[$className][$key] = isset($build[$className][$key]) ? $build[$className][$key]:$defaultValue; + } + } + + $build[$className]['className'] = $className; + $build[$className]['tableName'] = $tableName; + $build[$className]['columns'] = $columns; + + // Make sure that anything else that is specified in the schema makes it to the final array + $build[$className] = IPF_ORM_Utils::arrayDeepMerge($table, $build[$className]); + + // We need to keep track of the className for the connection + $build[$className]['connectionClassName'] = $build[$className]['className']; + } + + return $build; + } + + protected function _processInheritance($array) + { + // Apply default inheritance configuration + foreach ($array as $className => $definition) { + if ( ! empty($array[$className]['inheritance'])) { + $this->_validateSchemaElement('inheritance', array_keys($definition['inheritance']), $className . '->inheritance'); + + // Default inheritance to concrete inheritance + if ( ! isset($array[$className]['inheritance']['type'])) { + $array[$className]['inheritance']['type'] = 'concrete'; + } + + // Some magic for setting up the keyField and keyValue column aggregation options + // Adds keyField to the parent class automatically + if ($array[$className]['inheritance']['type'] == 'column_aggregation') { + // Set the keyField to 'type' by default + if ( ! isset($array[$className]['inheritance']['keyField'])) { + $array[$className]['inheritance']['keyField'] = 'type'; + } + + // Set the keyValue to the name of the child class if it does not exist + if ( ! isset($array[$className]['inheritance']['keyValue'])) { + $array[$className]['inheritance']['keyValue'] = $className; + } + + // Add the keyType column to the parent if a definition does not already exist + if ( ! isset($array[$array[$className]['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']])) { + $array[$definition['inheritance']['extends']]['columns'][$array[$className]['inheritance']['keyField']] = array('name' => $array[$className]['inheritance']['keyField'], 'type' => 'string', 'length' => 255); + } + } + } + } + + // Array of the array keys to move to the parent, and the value to default the child definition to + // after moving it. Will also populate the subclasses array for the inheritance parent + $moves = array('columns' => array()); + + foreach ($array as $className => $definition) { + if (!isset($definition['className'])) + continue; + $parent = $this->_findBaseSuperClass($array, $definition['className']); + // Move any definitions on the schema to the parent + if (isset($definition['inheritance']['extends']) && isset($definition['inheritance']['type']) && ($definition['inheritance']['type'] == 'simple' || $definition['inheritance']['type'] == 'column_aggregation')) { + foreach ($moves as $move => $resetValue) { + $array[$parent][$move] = IPF_ORM_Utils::arrayDeepMerge($array[$parent][$move], $definition[$move]); + $array[$definition['className']][$move] = $resetValue; + } + + // Populate the parents subclasses + if ($definition['inheritance']['type'] == 'column_aggregation') { + $array[$parent]['inheritance']['subclasses'][$definition['className']] = array($definition['inheritance']['keyField'] => $definition['inheritance']['keyValue']); + } + } + } + + return $array; + } + + protected function _findBaseSuperClass($array, $class) + { + if (isset($array[$class]['inheritance']['extends'])) { + return $this->_findBaseSuperClass($array, $array[$class]['inheritance']['extends']); + } else { + return $class; + } + } + + protected function _buildRelationships($array, $extraAllwedReferences) + { + // Handle auto detecting relations by the names of columns + // User.contact_id will automatically create User hasOne Contact local => contact_id, foreign => id + foreach ($array as $className => $properties) { + if (isset($properties['columns']) && ! empty($properties['columns']) && isset($properties['detect_relations']) && $properties['detect_relations']) { + foreach ($properties['columns'] as $column) { + // Check if the column we are inflecting has a _id on the end of it before trying to inflect it and find + // the class name for the column + if (strpos($column['name'], '_id')) { + $columnClassName = IPF_ORM_Inflector::classify(str_replace('_id', '', $column['name'])); + if (isset($array[$columnClassName]) && !isset($array[$className]['relations'][$columnClassName])) { + $array[$className]['relations'][$columnClassName] = array(); + + // Set the detected foreign key type and length to the same as the primary key + // of the related table + $type = isset($array[$columnClassName]['columns']['id']['type']) ? $array[$columnClassName]['columns']['id']['type']:'integer'; + $length = isset($array[$columnClassName]['columns']['id']['length']) ? $array[$columnClassName]['columns']['id']['length']:8; + $array[$className]['columns'][$column['name']]['type'] = $type; + $array[$className]['columns'][$column['name']]['length'] = $length; + } + } + } + } + } + + foreach ($array as $name => $properties) { + if (!isset($properties['relations'])) { + continue; + } + + $className = $properties['className']; + $relations = $properties['relations']; + + foreach ($relations as $alias => $relation) { + $class = isset($relation['class']) ? $relation['class'] : $alias; + if (!isset($array[$class]) && !in_array($class, $extraAllwedReferences)) { + print "Warning: Ignoring relation to unknown model '$class' in model '$className'. \n"; + continue; + } + $relation['class'] = $class; + $relation['alias'] = isset($relation['alias']) ? $relation['alias'] : $alias; + + // Attempt to guess the local and foreign + if (isset($relation['refClass'])) { + $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($name) . '_id'; + $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:IPF_ORM_Inflector::tableize($class) . '_id'; + } else { + $relation['local'] = isset($relation['local']) ? $relation['local']:IPF_ORM_Inflector::tableize($relation['class']) . '_id'; + $relation['foreign'] = isset($relation['foreign']) ? $relation['foreign']:'id'; + } + + if (isset($relation['refClass'])) { + $relation['type'] = 'many'; + } + + if (isset($relation['type']) && $relation['type']) { + $relation['type'] = $relation['type'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY; + } else { + $relation['type'] = IPF_ORM_Relation::ONE; + } + + if (isset($relation['foreignType']) && $relation['foreignType']) { + $relation['foreignType'] = $relation['foreignType'] === 'one' ? IPF_ORM_Relation::ONE:IPF_ORM_Relation::MANY; + } + + $relation['key'] = $this->_buildUniqueRelationKey($relation); + + $this->_validateSchemaElement('relation', array_keys($relation), $className . '->relation->' . $relation['alias']); + + $this->_relations[$className][$alias] = $relation; + } + } + + // Now we auto-complete opposite ends of relationships + $this->_autoCompleteOppositeRelations($extraAllwedReferences); + + // Make sure we do not have any duplicate relations + $this->_fixDuplicateRelations(); + + //$array['relations']; + // Set the full array of relationships for each class to the final array + foreach ($this->_relations as $className => $relations) { + if (!in_array($className, $extraAllwedReferences)) + $array[$className]['relations'] = $relations; + } + + return $array; + } + + protected function _autoCompleteOppositeRelations($extraAllwedReferences) + { + foreach ($this->_relations as $className => $relations) { + if (in_array($className, $extraAllwedReferences)) + continue; + + foreach ($relations as $alias => $relation) { + if ((isset($relation['equal']) && $relation['equal']) || (isset($relation['autoComplete']) && $relation['autoComplete'] === false)) { + continue; + } + + $newRelation = array(); + $newRelation['foreign'] = $relation['local']; + $newRelation['local'] = $relation['foreign']; + $newRelation['class'] = isset($relation['foreignClass']) ? $relation['foreignClass'] : $className; + $newRelation['alias'] = isset($relation['foreignAlias']) ? $relation['foreignAlias'] : $className; + $newRelation['exclude'] = isset($relation['foreignExclude']) ? $relation['foreignExclude'] : false; + + // this is so that we know that this relation was autogenerated and + // that we do not need to include it if it is explicitly declared in the schema by the users. + $newRelation['autogenerated'] = true; + + if (isset($relation['refClass'])) { + $newRelation['refClass'] = $relation['refClass']; + $newRelation['type'] = isset($relation['foreignType']) ? $relation['foreignType']:$relation['type']; + } else { + if(isset($relation['foreignType'])) { + $newRelation['type'] = $relation['foreignType']; + } else { + $newRelation['type'] = $relation['type'] === IPF_ORM_Relation::ONE ? IPF_ORM_Relation::MANY:IPF_ORM_Relation::ONE; + } + } + + // Make sure it doesn't already exist + if ( ! isset($this->_relations[$relation['class']][$newRelation['alias']])) { + $newRelation['key'] = $this->_buildUniqueRelationKey($newRelation); + $this->_relations[$relation['class']][$newRelation['alias']] = $newRelation; + } + } + } + } + + protected function _fixDuplicateRelations() + { + foreach($this->_relations as $className => $relations) { + // This is for checking for duplicates between alias-relations and a auto-generated relations to ensure the result set of unique relations + $existingRelations = array(); + $uniqueRelations = array(); + foreach ($relations as $relation) { + if ( ! in_array($relation['key'], $existingRelations)) { + $existingRelations[] = $relation['key']; + $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation)); + } else { + // check to see if this relationship is not autogenerated, if it's not, then the user must have explicitly declared it + if ( ! isset($relation['autogenerated']) || $relation['autogenerated'] != true) { + $uniqueRelations = array_merge($uniqueRelations, array($relation['alias'] => $relation)); + } + } + } + + $this->_relations[$className] = $uniqueRelations; + } + } + + protected function _buildUniqueRelationKey($relation) + { + return md5($relation['local'].$relation['foreign'].$relation['class'].(isset($relation['refClass']) ? $relation['refClass']:null)); + } + + protected function _validateSchemaElement($name, $element, $path) + { + $element = (array) $element; + + $validation = $this->_validation[$name]; + + // Validators are a part of the column validation + // This should be fixed, made cleaner + if ($name == 'column') { + $validators = $this->getValidators(); + $validation = array_merge($validation, $validators); + } + + $validation = array_flip($validation); + foreach ($element as $key => $value) { + if ( ! isset($validation[$value])) { + throw new IPF_ORM_Exception(sprintf('Invalid schema element named "' . $value . + '" at path "' . $path . '"')); + } + } + } +} + diff --git a/src/orm/inflector.php b/src/orm/inflector.php new file mode 100644 index 0000000..4946ac5 --- /dev/null +++ b/src/orm/inflector.php @@ -0,0 +1,341 @@ + '\1zes', + '/^(ox)$/i' => '\1en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)ix|ex$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(buffal|tomat)o$/i' => '\1oes', + '/(bu)s$/i' => '\1ses', + '/(alias|status)/i' => '\1es', + '/(octop|vir)us$/i' => '\1i', + '/(ax|test)is$/i' => '\1es', + '/s$/i' => 's', + '/$/' => 's'); + + $uncountable = array('equipment', + 'information', + 'rice', + 'money', + 'species', + 'series', + 'fish', + 'sheep'); + + $irregular = array('person' => 'people', + 'man' => 'men', + 'child' => 'children', + 'sex' => 'sexes', + 'move' => 'moves'); + + $lowercasedWord = strtolower($word); + + foreach ($uncountable as $_uncountable) { + if (substr($lowercasedWord, (-1 * strlen($_uncountable))) == $_uncountable) { + return $word; + } + } + + foreach ($irregular as $_plural=> $_singular) { + if (preg_match('/('.$_plural.')$/i', $word, $arr)) { + return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1) . substr($_singular,1), $word); + } + } + + foreach ($plural as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return false; + } + + public static function singularize($word) + { + $singular = array('/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\\1ix', + '/(vert|ind)ices$/i' => '\\1ex', + '/^(ox)en/i' => '\\1', + '/(alias|status)es$/i' => '\\1', + '/([octop|vir])i$/i' => '\\1us', + '/(cris|ax|test)es$/i' => '\\1is', + '/(shoe)s$/i' => '\\1', + '/(o)es$/i' => '\\1', + '/(bus)es$/i' => '\\1', + '/([m|l])ice$/i' => '\\1ouse', + '/(x|ch|ss|sh)es$/i' => '\\1', + '/(m)ovies$/i' => '\\1ovie', + '/(s)eries$/i' => '\\1eries', + '/([^aeiouy]|qu)ies$/i' => '\\1y', + '/([lr])ves$/i' => '\\1f', + '/(tive)s$/i' => '\\1', + '/(hive)s$/i' => '\\1', + '/([^f])ves$/i' => '\\1fe', + '/(^analy)ses$/i' => '\\1sis', + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\\1\\2sis', + '/([ti])a$/i' => '\\1um', + '/(n)ews$/i' => '\\1ews', + '/^(.{2,2})$/i' => '\\1', + '/s$/i' => ''); + + $uncountable = array('equipment', + 'information', + 'rice', + 'money', + 'species', + 'series', + 'fish', + 'sheep', + 'sms', + 'status', + 'access'); + + $irregular = array('person' => 'people', + 'man' => 'men', + 'child' => 'children', + 'sex' => 'sexes', + 'move' => 'moves'); + + $lowercasedWord = strtolower($word); + foreach ($uncountable as $_uncountable) { + if (substr($lowercasedWord, ( -1 * strlen($_uncountable))) == $_uncountable) { + return $word; + } + } + + foreach ($irregular as $_singular => $_plural) { + if (preg_match('/('.$_plural.')$/i', $word, $arr)) { + return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1).substr($_singular,1), $word); + } + } + + foreach ($singular as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return $word; + } + + public static function tableize($word) + { + // Would prefer this but it breaks unit tests. Forces the table underscore pattern + // return self::pluralize(self::underscore($name)); + return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); + } + + public static function classify($word) + { + return preg_replace_callback('~(_?)(_)([\w])~', array("IPF_ORM_Inflector", "classifyCallback"), ucfirst(strtolower($word))); + } + + public static function classifyCallback($matches) + { + return $matches[1] . strtoupper($matches[3]); + } + + public static function seemsUtf8($string) + { + for ($i = 0; $i < strlen($string); $i++) { + if (ord($string[$i]) < 0x80) continue; # 0bbbbbbb + elseif ((ord($string[$i]) & 0xE0) == 0xC0) $n=1; # 110bbbbb + elseif ((ord($string[$i]) & 0xF0) == 0xE0) $n=2; # 1110bbbb + elseif ((ord($string[$i]) & 0xF8) == 0xF0) $n=3; # 11110bbb + elseif ((ord($string[$i]) & 0xFC) == 0xF8) $n=4; # 111110bb + elseif ((ord($string[$i]) & 0xFE) == 0xFC) $n=5; # 1111110b + else return false; # Does not match any model + for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ? + if ((++$i == strlen($string)) || ((ord($string[$i]) & 0xC0) != 0x80)) + return false; + } + } + return true; + } + + public static function unaccent($string) + { + if (!preg_match('/[\x80-\xff]/', $string)) { + return $string; + } + + if (self::seemsUtf8($string)) { + $chars = array( + // Decompositions for Latin-1 Supplement + chr(195).chr(128) => 'A', chr(195).chr(129) => 'A', + chr(195).chr(130) => 'A', chr(195).chr(131) => 'A', + chr(195).chr(132) => 'A', chr(195).chr(133) => 'A', + chr(195).chr(135) => 'C', chr(195).chr(136) => 'E', + chr(195).chr(137) => 'E', chr(195).chr(138) => 'E', + chr(195).chr(139) => 'E', chr(195).chr(140) => 'I', + chr(195).chr(141) => 'I', chr(195).chr(142) => 'I', + chr(195).chr(143) => 'I', chr(195).chr(145) => 'N', + chr(195).chr(146) => 'O', chr(195).chr(147) => 'O', + chr(195).chr(148) => 'O', chr(195).chr(149) => 'O', + chr(195).chr(150) => 'O', chr(195).chr(153) => 'U', + chr(195).chr(154) => 'U', chr(195).chr(155) => 'U', + chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y', + chr(195).chr(159) => 's', chr(195).chr(160) => 'a', + chr(195).chr(161) => 'a', chr(195).chr(162) => 'a', + chr(195).chr(163) => 'a', chr(195).chr(164) => 'a', + chr(195).chr(165) => 'a', chr(195).chr(167) => 'c', + chr(195).chr(168) => 'e', chr(195).chr(169) => 'e', + chr(195).chr(170) => 'e', chr(195).chr(171) => 'e', + chr(195).chr(172) => 'i', chr(195).chr(173) => 'i', + chr(195).chr(174) => 'i', chr(195).chr(175) => 'i', + chr(195).chr(177) => 'n', chr(195).chr(178) => 'o', + chr(195).chr(179) => 'o', chr(195).chr(180) => 'o', + chr(195).chr(181) => 'o', chr(195).chr(182) => 'o', + chr(195).chr(182) => 'o', chr(195).chr(185) => 'u', + chr(195).chr(186) => 'u', chr(195).chr(187) => 'u', + chr(195).chr(188) => 'u', chr(195).chr(189) => 'y', + chr(195).chr(191) => 'y', + // Decompositions for Latin Extended-A + chr(196).chr(128) => 'A', chr(196).chr(129) => 'a', + chr(196).chr(130) => 'A', chr(196).chr(131) => 'a', + chr(196).chr(132) => 'A', chr(196).chr(133) => 'a', + chr(196).chr(134) => 'C', chr(196).chr(135) => 'c', + chr(196).chr(136) => 'C', chr(196).chr(137) => 'c', + chr(196).chr(138) => 'C', chr(196).chr(139) => 'c', + chr(196).chr(140) => 'C', chr(196).chr(141) => 'c', + chr(196).chr(142) => 'D', chr(196).chr(143) => 'd', + chr(196).chr(144) => 'D', chr(196).chr(145) => 'd', + chr(196).chr(146) => 'E', chr(196).chr(147) => 'e', + chr(196).chr(148) => 'E', chr(196).chr(149) => 'e', + chr(196).chr(150) => 'E', chr(196).chr(151) => 'e', + chr(196).chr(152) => 'E', chr(196).chr(153) => 'e', + chr(196).chr(154) => 'E', chr(196).chr(155) => 'e', + chr(196).chr(156) => 'G', chr(196).chr(157) => 'g', + chr(196).chr(158) => 'G', chr(196).chr(159) => 'g', + chr(196).chr(160) => 'G', chr(196).chr(161) => 'g', + chr(196).chr(162) => 'G', chr(196).chr(163) => 'g', + chr(196).chr(164) => 'H', chr(196).chr(165) => 'h', + chr(196).chr(166) => 'H', chr(196).chr(167) => 'h', + chr(196).chr(168) => 'I', chr(196).chr(169) => 'i', + chr(196).chr(170) => 'I', chr(196).chr(171) => 'i', + chr(196).chr(172) => 'I', chr(196).chr(173) => 'i', + chr(196).chr(174) => 'I', chr(196).chr(175) => 'i', + chr(196).chr(176) => 'I', chr(196).chr(177) => 'i', + chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij', + chr(196).chr(180) => 'J', chr(196).chr(181) => 'j', + chr(196).chr(182) => 'K', chr(196).chr(183) => 'k', + chr(196).chr(184) => 'k', chr(196).chr(185) => 'L', + chr(196).chr(186) => 'l', chr(196).chr(187) => 'L', + chr(196).chr(188) => 'l', chr(196).chr(189) => 'L', + chr(196).chr(190) => 'l', chr(196).chr(191) => 'L', + chr(197).chr(128) => 'l', chr(197).chr(129) => 'L', + chr(197).chr(130) => 'l', chr(197).chr(131) => 'N', + chr(197).chr(132) => 'n', chr(197).chr(133) => 'N', + chr(197).chr(134) => 'n', chr(197).chr(135) => 'N', + chr(197).chr(136) => 'n', chr(197).chr(137) => 'N', + chr(197).chr(138) => 'n', chr(197).chr(139) => 'N', + chr(197).chr(140) => 'O', chr(197).chr(141) => 'o', + chr(197).chr(142) => 'O', chr(197).chr(143) => 'o', + chr(197).chr(144) => 'O', chr(197).chr(145) => 'o', + chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe', + chr(197).chr(148) => 'R', chr(197).chr(149) => 'r', + chr(197).chr(150) => 'R', chr(197).chr(151) => 'r', + chr(197).chr(152) => 'R', chr(197).chr(153) => 'r', + chr(197).chr(154) => 'S', chr(197).chr(155) => 's', + chr(197).chr(156) => 'S', chr(197).chr(157) => 's', + chr(197).chr(158) => 'S', chr(197).chr(159) => 's', + chr(197).chr(160) => 'S', chr(197).chr(161) => 's', + chr(197).chr(162) => 'T', chr(197).chr(163) => 't', + chr(197).chr(164) => 'T', chr(197).chr(165) => 't', + chr(197).chr(166) => 'T', chr(197).chr(167) => 't', + chr(197).chr(168) => 'U', chr(197).chr(169) => 'u', + chr(197).chr(170) => 'U', chr(197).chr(171) => 'u', + chr(197).chr(172) => 'U', chr(197).chr(173) => 'u', + chr(197).chr(174) => 'U', chr(197).chr(175) => 'u', + chr(197).chr(176) => 'U', chr(197).chr(177) => 'u', + chr(197).chr(178) => 'U', chr(197).chr(179) => 'u', + chr(197).chr(180) => 'W', chr(197).chr(181) => 'w', + chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y', + chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z', + chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z', + chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z', + chr(197).chr(190) => 'z', chr(197).chr(191) => 's', + // Euro Sign + chr(226).chr(130).chr(172) => 'E', + // GBP (Pound) Sign + chr(194).chr(163) => ''); + + $string = strtr($string, $chars); + } else { + // Assume ISO-8859-1 if not UTF-8 + $chars['in'] = chr(128).chr(131).chr(138).chr(142).chr(154).chr(158) + .chr(159).chr(162).chr(165).chr(181).chr(192).chr(193).chr(194) + .chr(195).chr(196).chr(197).chr(199).chr(200).chr(201).chr(202) + .chr(203).chr(204).chr(205).chr(206).chr(207).chr(209).chr(210) + .chr(211).chr(212).chr(213).chr(214).chr(216).chr(217).chr(218) + .chr(219).chr(220).chr(221).chr(224).chr(225).chr(226).chr(227) + .chr(228).chr(229).chr(231).chr(232).chr(233).chr(234).chr(235) + .chr(236).chr(237).chr(238).chr(239).chr(241).chr(242).chr(243) + .chr(244).chr(245).chr(246).chr(248).chr(249).chr(250).chr(251) + .chr(252).chr(253).chr(255); + + $chars['out'] = "EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy"; + + $string = strtr($string, $chars['in'], $chars['out']); + $doubleChars['in'] = array(chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254)); + $doubleChars['out'] = array('OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'); + $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); + } + + return $string; + } + + public static function translit($st) + { + $st = strtr($st, array( + 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', + 'е' => 'e', 'ё' => 'e', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', + 'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', + 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', + 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'ts', 'ч' => 'ch', + 'ш' => 'sh', 'щ' => 'shch', 'ъ' => '\'', 'ы' => 'i', 'ь' => '', + 'э' => 'e', 'ю' => 'yu', 'я' => 'ya', 'ї' => 'i', 'є' => 'ie', + + 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', + 'Е' => 'E', 'Ё' => 'E', 'Ж' => 'Zh', 'З' => 'Z', 'И' => 'I', + 'Й' => 'Y', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', + 'О' => 'O', 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', + 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'Ts', 'Ч' => 'Ch', + 'Ш' => 'Sh', 'Щ' => 'Shch', 'Ъ' => '\'', 'Ы' => 'I', 'Ь' => '', + 'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya', 'Ї' => 'Yi', 'Є' => 'Ye', + )); + return $st; + } + + public static function urlize($text) + { + // Remove all non url friendly characters with the unaccent function + $text = self::unaccent($text); + + // Transliterate cyrillic + $text = self::translit($text); + + // Remove all none word characters + $text = preg_replace('/\W/', ' ', $text); + + // More stripping. Replace spaces with dashes + $text = strtolower(preg_replace('/[^A-Z^a-z^0-9^\/]+/', '-', + preg_replace('/([a-z\d])([A-Z])/', '\1_\2', + preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', + preg_replace('/::/', '/', $text))))); + + return trim($text, '-'); + } +} + diff --git a/src/orm/manager.php b/src/orm/manager.php new file mode 100644 index 0000000..570de33 --- /dev/null +++ b/src/orm/manager.php @@ -0,0 +1,63 @@ +attributes = array( + IPF_ORM::ATTR_LOAD_REFERENCES => true, + IPF_ORM::ATTR_PORTABILITY => IPF_ORM::PORTABILITY_ALL, + IPF_ORM::ATTR_EXPORT => IPF_ORM::EXPORT_ALL, + IPF_ORM::ATTR_DECIMAL_PLACES => 2, + IPF_ORM::ATTR_DEFAULT_PARAM_NAMESPACE => 'ipf', + ); + } + + public static function getInstance() + { + static $instance; + if ( ! isset($instance)) { + $instance = new self(); + } + return $instance; + } + + public function getQueryRegistry() + { + if ( ! isset($this->_queryRegistry)) { + $this->_queryRegistry = new IPF_ORM_Query_Registry; + } + return $this->_queryRegistry; + } + + public function setQueryRegistry(IPF_ORM_Query_Registry $registry) + { + $this->_queryRegistry = $registry; + + return $this; + } + + public static function connection() + { + return IPF_ORM_Manager::getInstance()->getCurrentConnection(); + } + + public function getCurrentConnection() + { + if (!$this->_connection) { + $pdo = \PFF\Container::databaseConnection(); + $this->_connection = new IPF_ORM_Connection_Mysql($this, $pdo); + } + return $this->_connection; + } + + public function __toString() + { + return "
\nIPF_ORM_Manager\n
"; + } +} + diff --git a/src/orm/null.php b/src/orm/null.php new file mode 100644 index 0000000..6347618 --- /dev/null +++ b/src/orm/null.php @@ -0,0 +1,36 @@ +_setExecuted(false); + + $this->_setQuery($query); + $this->_setPage($page); + + $this->setMaxPerPage($maxPerPage); + } + + protected function _initialize($params = array()) + { + // retrieve the number of items found + $count = $this->getCountQuery()->count($this->getCountQueryParams($params)); + + $this->_setNumResults($count); + $this->_setExecuted(true); // _adjustOffset relies of _executed equals true = getNumResults() + + $this->_adjustOffset(); + } + + protected function _adjustOffset() + { + // Define new total of pages + $this->_setLastPage( + max(1, ceil($this->getNumResults() / $this->getMaxPerPage())) + ); + $offset = ($this->getPage() - 1) * $this->getMaxPerPage(); + + // Assign new offset and limit to IPF_ORM_Query object + $p = $this->getQuery(); + $p->offset($offset); + $p->limit($this->getMaxPerPage()); + } + + public function getExecuted() + { + return $this->_executed; + } + + protected function _setExecuted($executed) + { + $this->_executed = $executed; + } + + public function getRange($rangeStyle, $options = array()) + { + $class = 'IPF_ORM_Pager_Range_' . ucfirst($rangeStyle); + return new $class($options, $this); + } + + public function getNumResults() + { + if ($this->getExecuted()) { + return $this->_numResults; + } + throw new IPF_ORM_Exception( + 'Cannot retrieve the number of results of a not yet executed Pager query' + ); + } + + protected function _setNumResults($nb) + { + $this->_numResults = $nb; + } + + public function getFirstPage() + { + return 1; + } + + public function getLastPage() + { + if ($this->getExecuted()) { + return $this->_lastPage; + } + + throw new IPF_ORM_Exception( + 'Cannot retrieve the last page number of a not yet executed Pager query' + ); + } + + protected function _setLastPage($page) + { + $this->_lastPage = $page; + + if ($this->getPage() > $page) { + $this->_setPage($page); + } + } + + public function getPage() + { + return $this->_page; + } + + public function getNextPage() + { + if ($this->getExecuted()) { + return min($this->getPage() + 1, $this->getLastPage()); + } + + throw new IPF_ORM_Exception( + 'Cannot retrieve the last page number of a not yet executed Pager query' + ); + } + + public function getPreviousPage() + { + if ($this->getExecuted()) { + return max($this->getPage() - 1, $this->getFirstPage()); + } + + throw new IPF_ORM_Exception( + 'Cannot retrieve the previous page number of a not yet executed Pager query' + ); + } + + public function getFirstIndice() + { + return ($this->getPage() - 1) * $this->getMaxPerPage() + 1; + } + + public function getLastIndice() + { + return min($this->getNumResults(), ($this->getPage() * $this->getMaxPerPage())); + } + + public function haveToPaginate() + { + if ($this->getExecuted()) { + return $this->getNumResults() > $this->getMaxPerPage(); + } + + throw new IPF_ORM_Exception( + 'Cannot know if it is necessary to paginate a not yet executed Pager query' + ); + } + + public function setPage($page) + { + $this->_setPage($page); + $this->_setExecuted(false); + } + + private function _setPage($page) + { + $page = intval($page); + $this->_page = ($page <= 0) ? 1 : $page; + } + + public function getMaxPerPage() + { + return $this->_maxPerPage; + } + + public function setMaxPerPage($max) + { + if ($max > 0) { + $this->_maxPerPage = $max; + } else if ($max == 0) { + $this->_maxPerPage = 25; + } else { + $this->_maxPerPage = abs($max); + } + + $this->_setExecuted(false); + } + + public function getResultsInPage() + { + $page = $this->getPage(); + + if ($page != $this->getLastPage()) { + return $this->getMaxPerPage(); + } + + $offset = ($this->getPage() - 1) * $this->getMaxPerPage(); + + return abs($this->getNumResults() - $offset); + } + + public function getQuery() + { + return $this->_query; + } + + protected function _setQuery($query) + { + if (is_string($query)) { + $query = IPF_ORM_Query::create()->parseQuery($query); + } + + $this->_query = $query; + } + + public function getCountQuery() + { + return ($this->_countQuery !== null) ? $this->_countQuery : $this->_query; + } + + public function setCountQuery($query, $params = null) + { + if (is_string($query)) { + $query = IPF_ORM_Query::create()->parseQuery($query); + } + + $this->_countQuery = $query; + + $this->setCountQueryParams($params); + + $this->_setExecuted(false); + } + + public function getCountQueryParams($defaultParams = array()) + { + return ($this->_countQueryParams !== null) ? $this->_countQueryParams : $defaultParams; + } + + public function setCountQueryParams($params = array(), $append = false) + { + if ($append && is_array($this->_countQueryParams)) { + $this->_countQueryParams = array_merge($this->_countQueryParams, $params); + } else { + if ($params !== null && !is_array($params)) { + $params = array($params); + } + + $this->_countQueryParams = $params; + } + + $this->_setExecuted(false); + } + + public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD) + { + if (!$this->getExecuted()) { + $this->_initialize($params); + } + return $this->getQuery()->execute($params, $hydrationMode); + } + + public function __debugInfo() + { + return array( + 'page' => $this->getPage(), + 'size' => $this->getMaxPerPage(), + 'query' => $this->getQuery(), + ); + } +} + diff --git a/src/orm/pager/layout.php b/src/orm/pager/layout.php new file mode 100644 index 0000000..a5c1a32 --- /dev/null +++ b/src/orm/pager/layout.php @@ -0,0 +1,236 @@ +_setPager($pager); + $this->_setPagerRange($pagerRange); + $this->_setUrlMask($urlMask); + + $this->setTemplate('[{%page}]'); + $this->setSelectedTemplate(''); + $this->setSeparatorTemplate(''); + } + + public function getPager() + { + return $this->_pager; + } + + protected function _setPager($pager) + { + $this->_pager = $pager; + } + + public function execute($params = array(), $hydrationMode = IPF_ORM::FETCH_RECORD) + { + return $this->getPager()->execute($params, $hydrationMode); + } + + public function getPagerRange() + { + return $this->_pagerRange; + } + + protected function _setPagerRange($pagerRange) + { + $this->_pagerRange = $pagerRange; + $this->getPagerRange()->setPager($this->getPager()); + } + + public function getUrlMask() + { + return $this->_urlMask; + } + + protected function _setUrlMask($urlMask) + { + $this->_urlMask = $urlMask; + } + + public function getTemplate() + { + return $this->_template; + } + + public function setTemplate($template) + { + $this->_template = $template; + } + + public function getSelectedTemplate() + { + return $this->_selectedTemplate; + } + + public function setSelectedTemplate($selectedTemplate) + { + $this->_selectedTemplate = $selectedTemplate; + } + + public function getSeparatorTemplate() + { + return $this->_separatorTemplate; + } + + public function setSeparatorTemplate($separatorTemplate) + { + $this->_separatorTemplate = $separatorTemplate; + } + + public function addMaskReplacement($oldMask, $newMask, $asValue = false) + { + if (($oldMask = trim($oldMask)) != 'page_number') { + $this->_maskReplacements[$oldMask] = array( + 'newMask' => $newMask, + 'asValue' => ($asValue === false) ? false : true + ); + } + } + + public function removeMaskReplacement($oldMask) + { + if (isset($this->_maskReplacements[$oldMask])) { + $this->_maskReplacements[$oldMask] = null; + unset($this->_maskReplacements[$oldMask]); + } + } + + public function cleanMaskReplacements() + { + $this->_maskReplacements = null; + $this->_maskReplacements = array(); + } + + public function display($options = array(), $return = true) + { + $range = $this->getPagerRange()->rangeAroundPage(); + $str = ''; + + // For each page in range + for ($i = 0, $l = count($range); $i < $l; $i++) { + // Define some optional mask values + $options['page_number'] = $range[$i]; + + $str .= $this->processPage($options); + + // Apply separator between pages + if ($i < $l - 1) { + $str .= $this->getSeparatorTemplate(); + } + } + + // Possible wish to return value instead of print it on screen + if ($return) { + return $str; + } + + echo $str; + } + + public function processPage($options = array()) + { + // Check if at least basic options are defined + if (!isset($options['page_number'])) { + throw new IPF_ORM_Exception( + 'Cannot process template of the given page. ' . + 'Missing at least one of needed parameters: \'page\' or \'page_number\'' + ); + + // Should never reach here + return ''; + } + + // Assign "page" options index if not defined yet + if (!isset($this->_maskReplacements['page']) && !isset($options['page'])) { + $options['page'] = $options['page_number']; + } + + return $this->_parseTemplate($options); + } + + public function __toString() + { + return $this->display(array(), true); + } + + protected function _parseTemplate($options = array()) + { + $str = $this->_parseUrlTemplate($options); + $replacements = $this->_parseReplacementsTemplate($options); + + return strtr($str, $replacements); + } + + protected function _parseUrlTemplate($options = array()) + { + $str = ''; + + // If given page is the current active one + if ($options['page_number'] == $this->getPager()->getPage()) { + $str = $this->_parseMaskReplacements($this->getSelectedTemplate()); + } + + // Possible attempt where Selected == Template + if ($str == '') { + $str = $this->_parseMaskReplacements($this->getTemplate()); + } + + return $str; + } + + protected function _parseReplacementsTemplate($options = array()) + { + // Defining "url" options index to allow {%url} mask + $options['url'] = $this->_parseUrl($options); + + $replacements = array(); + + foreach ($options as $k => $v) { + $replacements['{%'.$k.'}'] = $v; + } + + return $replacements; + } + + protected function _parseUrl($options = array()) + { + $str = $this->_parseMaskReplacements($this->getUrlMask()); + + $replacements = array(); + + foreach ($options as $k => $v) { + $replacements['{%'.$k.'}'] = $v; + } + + return strtr($str, $replacements); + } + + protected function _parseMaskReplacements($str) + { + $replacements = array(); + + foreach ($this->_maskReplacements as $k => $v) { + $replacements['{%'.$k.'}'] = ($v['asValue'] === true) ? $v['newMask'] : '{%'.$v['newMask'].'}'; + } + + return strtr($str, $replacements); + } + + public function __debugInfo() + { + return array( + 'pager' => $this->getPager() + ); + } +} + diff --git a/src/orm/pager/layoutarrows.php b/src/orm/pager/layoutarrows.php new file mode 100644 index 0000000..57c54d0 --- /dev/null +++ b/src/orm/pager/layoutarrows.php @@ -0,0 +1,64 @@ +getPager(); + $str = ''; + + $range = $this->getPagerRange()->rangeAroundPage(); + + if ($pager->getFirstPage() != $pager->getLastPage()) + { + $this->removeMaskReplacement('page'); + + if ($range[0] > 1) + { + $options['page_number'] = 1; + $str .= $this->processPage($options); + + if ($range[0] > 2) + { + $options['page_number'] = 2; + $str .= $this->processPage($options); + } + + if ($range[0]>3) + $str .= ' ... '; + } + + // Pages listing + $str .= parent::display($options, true); + + $range = $this->getPagerRange()->rangeAroundPage(); + $last_range = $range[count($range)-1]; + + if ($last_range < $pager->getLastPage()) + { + if ($last_range < $pager->getLastPage() - 2) + { + $str .= ' ... '; + } + + if ($last_range < $pager->getLastPage() - 1) + { + $options['page_number'] = $pager->getLastPage() - 1; + $str .= $this->processPage($options); + } + + if ($last_range < $pager->getLastPage()) + { + $options['page_number'] = $pager->getLastPage(); + $str .= $this->processPage($options); + } + } + } + + if ($return) + return $str; + + echo $str; + } +} + diff --git a/src/orm/pager/range.php b/src/orm/pager/range.php new file mode 100644 index 0000000..18ffd8f --- /dev/null +++ b/src/orm/pager/range.php @@ -0,0 +1,61 @@ +_setOptions($options); + + if ($pager !== null) { + $this->setPager($pager); + } + } + + public function getPager() + { + return $this->pager; + } + + public function setPager($pager) + { + $this->pager = $pager; + + // Lazy-load initialization. It only should be called when all + // needed information data is ready (this can only happens when we have + // options stored and a IPF_ORM_Pager assocated) + $this->_initialize(); + } + + public function getOptions() + { + return $this->_options; + } + + public function getOption($option) + { + if (isset($this->_options[$option])) { + return $this->_options[$option]; + } + + throw new IPF_ORM_Exception( + 'Cannot access unexistent option \'' . $option . '\' in IPF_ORM_Pager_Range class' + ); + } + + protected function _setOptions($options) + { + $this->_options = $options; + } + + public function isInRange($page) + { + return (array_search($page, $this->rangeAroundPage()) !== false); + } + + abstract protected function _initialize(); + + abstract public function rangeAroundPage(); +} diff --git a/src/orm/pager/range/jumping.php b/src/orm/pager/range/jumping.php new file mode 100644 index 0000000..4324921 --- /dev/null +++ b/src/orm/pager/range/jumping.php @@ -0,0 +1,51 @@ +_options['chunk'])) { + $this->_setChunkLength($this->_options['chunk']); + } else { + throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be define in options.'); + } + } + + public function getChunkLength() + { + return $this->_chunkLength; + } + + protected function _setChunkLength($chunkLength) + { + $this->_chunkLength = $chunkLength; + } + + public function rangeAroundPage() + { + $pager = $this->getPager(); + + if ($pager->getExecuted()) { + $page = $pager->getPage(); + + // Define initial assignments for StartPage and EndPage + $startPage = $page - ($page - 1) % $this->getChunkLength(); + $endPage = ($startPage + $this->getChunkLength()) - 1; + + // Check for EndPage out-range + if ($endPage > $pager->getLastPage()) { + $endPage = $pager->getLastPage(); + } + + // No need to check for out-range in start, it will never happens + + return range($startPage, $endPage); + } + + throw new IPF_ORM_Exception( + 'Cannot retrieve the range around the page of a not yet executed Pager query' + ); + } +} diff --git a/src/orm/pager/range/sliding.php b/src/orm/pager/range/sliding.php new file mode 100644 index 0000000..cf8198e --- /dev/null +++ b/src/orm/pager/range/sliding.php @@ -0,0 +1,66 @@ +_options['chunk'])) { + $this->_setChunkLength($this->_options['chunk']); + } else { + throw new IPF_ORM_Exception('Missing parameter \'chunk\' that must be defined in options.'); + } + } + + public function getChunkLength() + { + return $this->_chunkLength; + } + + protected function _setChunkLength($chunkLength) + { + $chunkLength = (int) $chunkLength; + if (!$chunkLength) { + $chunkLength = 1; + } else { + $this->_chunkLength = $chunkLength; + } + } + + public function rangeAroundPage() + { + $pager = $this->getPager(); + + if ($pager->getExecuted()) { + $page = $pager->getPage(); + $pages = $pager->getLastPage(); + + $chunk = $this->getChunkLength(); + + if ($chunk > $pages) { + $chunk = $pages; + } + + $chunkStart = $page - (floor($chunk / 2)); + $chunkEnd = $page + (ceil($chunk / 2)-1); + + if ($chunkStart < 1) { + $adjust = 1 - $chunkStart; + $chunkStart = 1; + $chunkEnd = $chunkEnd + $adjust; + } + + if ($chunkEnd > $pages) { + $adjust = $chunkEnd - $pages; + $chunkStart = $chunkStart - $adjust; + $chunkEnd = $pages; + } + return range($chunkStart, $chunkEnd); + } + + throw new IPF_ORM_Exception( + 'Cannot retrieve the range around the page of a not yet executed Pager query' + ); + } +} diff --git a/src/orm/query.php b/src/orm/query.php new file mode 100644 index 0000000..1830c33 --- /dev/null +++ b/src/orm/query.php @@ -0,0 +1,1259 @@ +_pendingJoinConditions = array(); + $this->_pendingSubqueries = array(); + $this->_pendingFields = array(); + $this->_neededTables = array(); + $this->_expressionMap = array(); + $this->_subqueryAliases = array(); + } + + public function createSubquery() + { + $class = get_class($this); + $obj = new $class(); + + // copy the aliases to the subquery + $obj->copyAliases($this); + + // this prevents the 'id' being selected, re ticket #307 + $obj->isSubquery(true); + + return $obj; + } + + protected function _addPendingJoinCondition($componentAlias, $joinCondition) + { + $this->_pendingJoins[$componentAlias] = $joinCondition; + } + + public function addEnumParam($key, $table = null, $column = null) + { + $array = (isset($table) || isset($column)) ? array($table, $column) : array(); + + if ($key === '?') { + $this->_enumParams[] = $array; + } else { + $this->_enumParams[$key] = $array; + } + } + + public function getEnumParams() + { + return $this->_enumParams; + } + + public function getDql() + { + $q = ''; + $q .= ( ! empty($this->_dqlParts['select']))? 'SELECT ' . implode(', ', $this->_dqlParts['select']) : ''; + $q .= ( ! empty($this->_dqlParts['from']))? ' FROM ' . implode(' ', $this->_dqlParts['from']) : ''; + $q .= ( ! empty($this->_dqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_dqlParts['where']) : ''; + $q .= ( ! empty($this->_dqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : ''; + $q .= ( ! empty($this->_dqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_dqlParts['having']) : ''; + $q .= ( ! empty($this->_dqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : ''; + $q .= ( ! empty($this->_dqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_dqlParts['limit']) : ''; + $q .= ( ! empty($this->_dqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_dqlParts['offset']) : ''; + + return $q; + } + + + public function fetchArray($params = array()) { + return $this->execute($params, IPF_ORM::HYDRATE_ARRAY); + } + + public function fetchOne($params = array(), $hydrationMode = null) + { + $collection = $this->execute($params, $hydrationMode); + + if (count($collection) === 0) { + return false; + } + + if ($collection instanceof IPF_ORM_Collection) { + return $collection->getFirst(); + } else if (is_array($collection)) { + return array_shift($collection); + } + + return false; + } + + public function isSubquery($bool = null) + { + if ($bool === null) { + return $this->_isSubquery; + } + + $this->_isSubquery = (bool) $bool; + return $this; + } + + public function getAggregateAlias($dqlAlias) + { + return $this->getSqlAggregateAlias($dqlAlias); + } + + public function getSqlAggregateAlias($dqlAlias) + { + if (isset($this->_aggregateAliasMap[$dqlAlias])) { + // mark the expression as used + $this->_expressionMap[$dqlAlias][1] = true; + + return $this->_aggregateAliasMap[$dqlAlias]; + } else if ( ! empty($this->_pendingAggregates)) { + $this->processPendingAggregates(); + + return $this->getSqlAggregateAlias($dqlAlias); + } else { + throw new IPF_ORM_Exception('Unknown aggregate alias: ' . $dqlAlias); + } + } + + public function getDqlPart($queryPart) + { + if ( ! isset($this->_dqlParts[$queryPart])) { + throw new IPF_ORM_Exception('Unknown query part ' . $queryPart); + } + + return $this->_dqlParts[$queryPart]; + } + + public function contains($dql) + { + return stripos($this->getDql(), $dql) === false ? false : true; + } + + public function processPendingFields($componentAlias) + { + $tableAlias = $this->getTableAlias($componentAlias); + $table = $this->_queryComponents[$componentAlias]['table']; + + if ( ! isset($this->_pendingFields[$componentAlias])) { + if ($this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) { + if ( ! $this->_isSubquery && $componentAlias == $this->getRootAlias()) { + throw new IPF_ORM_Exception("The root class of the query (alias $componentAlias) " + . " must have at least one field selected."); + } + } + return; + } + + // At this point we know the component is FETCHED (either it's the base class of + // the query (FROM xyz) or its a "fetch join"). + + // Check that the parent join (if there is one), is a "fetch join", too. + if (isset($this->_queryComponents[$componentAlias]['parent'])) { + $parentAlias = $this->_queryComponents[$componentAlias]['parent']; + if (is_string($parentAlias) && ! isset($this->_pendingFields[$parentAlias]) + && $this->_hydrator->getHydrationMode() != IPF_ORM::HYDRATE_NONE) { + throw new IPF_ORM_Exception("The left side of the join between " + . "the aliases '$parentAlias' and '$componentAlias' must have at least" + . " the primary key field(s) selected."); + } + } + + $fields = $this->_pendingFields[$componentAlias]; + + // check for wildcards + if (in_array('*', $fields)) { + $fields = $table->getFieldNames(); + } else { + // only auto-add the primary key fields if this query object is not + // a subquery of another query object + if ( ! $this->_isSubquery || $this->_hydrator->getHydrationMode() === IPF_ORM::HYDRATE_NONE) { + $fields = array_unique(array_merge((array) $table->getIdentifier(), $fields)); + } + } + + $sql = array(); + foreach ($fields as $fieldName) { + $columnName = $table->getColumnName($fieldName); + $sql[] = $this->_conn->quoteIdentifier($tableAlias . '.' . $columnName) + . ' AS ' + . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName); + } + + $this->_neededTables[] = $tableAlias; + + return implode(', ', $sql); + } + + public function parseSelectField($field) + { + $terms = explode('.', $field); + + if (isset($terms[1])) { + $componentAlias = $terms[0]; + $field = $terms[1]; + } else { + reset($this->_queryComponents); + $componentAlias = key($this->_queryComponents); + $fields = $terms[0]; + } + + $tableAlias = $this->getTableAlias($componentAlias); + $table = $this->_queryComponents[$componentAlias]['table']; + + + // check for wildcards + if ($field === '*') { + $sql = array(); + + foreach ($table->getColumnNames() as $field) { + $sql[] = $this->parseSelectField($componentAlias . '.' . $field); + } + + return implode(', ', $sql); + } else { + $name = $table->getColumnName($field); + + $this->_neededTables[] = $tableAlias; + + return $this->_conn->quoteIdentifier($tableAlias . '.' . $name) + . ' AS ' + . $this->_conn->quoteIdentifier($tableAlias . '__' . $name); + } + } + + public function getExpressionOwner($expr) + { + if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') { + preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches); + + $match = current($matches); + + if (isset($match[0])) { + $terms = explode('.', $match[0]); + + return $terms[0]; + } + } + return $this->getRootAlias(); + + } + + public function parseSelect($dql) + { + $refs = $this->_tokenizer->sqlExplode($dql, ','); + + $pos = strpos(trim($refs[0]), ' '); + $first = substr($refs[0], 0, $pos); + + // check for DISTINCT keyword + if ($first === 'DISTINCT') { + $this->_sqlParts['distinct'] = true; + + $refs[0] = substr($refs[0], ++$pos); + } + + $parsedComponents = array(); + + foreach ($refs as $reference) { + $reference = trim($reference); + + if (empty($reference)) { + continue; + } + + $terms = $this->_tokenizer->sqlExplode($reference, ' '); + + $pos = strpos($terms[0], '('); + + if (count($terms) > 1 || $pos !== false) { + $expression = array_shift($terms); + $alias = array_pop($terms); + + if ( ! $alias) { + $alias = substr($expression, 0, $pos); + } + + $componentAlias = $this->getExpressionOwner($expression); + $expression = $this->parseClause($expression); + + $tableAlias = $this->getTableAlias($componentAlias); + + $index = count($this->_aggregateAliasMap); + + $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index); + + $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias; + + $this->_aggregateAliasMap[$alias] = $sqlAlias; + $this->_expressionMap[$alias][0] = $expression; + + $this->_queryComponents[$componentAlias]['agg'][$index] = $alias; + + $this->_neededTables[] = $tableAlias; + } else { + $e = explode('.', $terms[0]); + + if (isset($e[1])) { + $componentAlias = $e[0]; + $field = $e[1]; + } else { + reset($this->_queryComponents); + $componentAlias = key($this->_queryComponents); + $field = $e[0]; + } + + $this->_pendingFields[$componentAlias][] = $field; + } + } + } + + public function parseClause($clause) + { + $clause = trim($clause); + + if (is_numeric($clause)) { + return $clause; + } + + $terms = $this->_tokenizer->clauseExplode($clause, array(' ', '+', '-', '*', '/', '<', '>', '=', '>=', '<=')); + + $str = ''; + foreach ($terms as $term) { + $pos = strpos($term[0], '('); + + if ($pos !== false) { + $name = substr($term[0], 0, $pos); + $term[0] = $this->parseFunctionExpression($term[0]); + } else { + if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") { + + if (strpos($term[0], '.') !== false) { + if ( ! is_numeric($term[0])) { + $e = explode('.', $term[0]); + + $field = array_pop($e); + + if ($this->getType() === IPF_ORM_Query::SELECT) { + $componentAlias = implode('.', $e); + + if (empty($componentAlias)) { + $componentAlias = $this->getRootAlias(); + } + + $this->load($componentAlias); + + // check the existence of the component alias + if ( ! isset($this->_queryComponents[$componentAlias])) { + throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias); + } + + $table = $this->_queryComponents[$componentAlias]['table']; + + $def = $table->getDefinitionOf($field); + + // get the actual field name from alias + $field = $table->getColumnName($field); + + // check column existence + if ( ! $def) { + throw new IPF_ORM_Exception('Unknown column ' . $field); + } + + if (isset($def['owner'])) { + $componentAlias = $componentAlias . '.' . $def['owner']; + } + + $tableAlias = $this->getTableAlias($componentAlias); + + // build sql expression + $term[0] = $this->_conn->quoteIdentifier($tableAlias) + . '.' + . $this->_conn->quoteIdentifier($field); + } else { + // build sql expression + $field = $this->getRoot()->getColumnName($field); + $term[0] = $this->_conn->quoteIdentifier($field); + } + } + } else { + if ( ! empty($term[0]) && + ! in_array(strtoupper($term[0]), self::$_keywords) && + ! is_numeric($term[0])) { + + $componentAlias = $this->getRootAlias(); + + $found = false; + + if ($componentAlias !== false && + $componentAlias !== null) { + $table = $this->_queryComponents[$componentAlias]['table']; + + // check column existence + if ($table->hasField($term[0])) { + $found = true; + + $def = $table->getDefinitionOf($term[0]); + + // get the actual column name from field name + $term[0] = $table->getColumnName($term[0]); + + + if (isset($def['owner'])) { + $componentAlias = $componentAlias . '.' . $def['owner']; + } + + $tableAlias = $this->getTableAlias($componentAlias); + + if ($this->getType() === IPF_ORM_Query::SELECT) { + // build sql expression + $term[0] = $this->_conn->quoteIdentifier($tableAlias) + . '.' + . $this->_conn->quoteIdentifier($term[0]); + } else { + // build sql expression + $term[0] = $this->_conn->quoteIdentifier($term[0]); + } + } else { + $found = false; + } + } + + if ( ! $found) { + $term[0] = $this->getSqlAggregateAlias($term[0]); + } + } + } + } + } + + $str .= $term[0] . $term[1]; + } + return $str; + } + + public function parseIdentifierReference($expr) + { + } + + public function parseFunctionExpression($expr) + { + $pos = strpos($expr, '('); + + $name = substr($expr, 0, $pos); + + if ($name === '') { + return $this->parseSubquery($expr); + } + + $argStr = substr($expr, ($pos + 1), -1); + + $args = array(); + // parse args + + foreach ($this->_tokenizer->sqlExplode($argStr, ',') as $arg) { + $args[] = $this->parseClause($arg); + } + + // convert DQL function to its RDBMS specific equivalent + try { + $expr = call_user_func_array(array($this->_conn->expression, $name), $args); + } catch (IPF_ORM_Exception $e) { + throw new IPF_ORM_Exception('Unknown function ' . $name . '.'); + } + + return $expr; + } + + public function parseSubquery($subquery) + { + $trimmed = trim($this->_tokenizer->bracketTrim($subquery)); + + // check for possible subqueries + if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') { + // parse subquery + $trimmed = $this->createSubquery()->parseDqlQuery($trimmed)->getQuery(); + } else { + // parse normal clause + $trimmed = $this->parseClause($trimmed); + } + + return '(' . $trimmed . ')'; + } + + public function processPendingSubqueries() + { + foreach ($this->_pendingSubqueries as $value) { + list($dql, $alias) = $value; + + $subquery = $this->createSubquery(); + + $sql = $subquery->parseDqlQuery($dql, false)->getQuery(); + + reset($this->_queryComponents); + $componentAlias = key($this->_queryComponents); + $tableAlias = $this->getTableAlias($componentAlias); + + $sqlAlias = $tableAlias . '__' . count($this->_aggregateAliasMap); + + $this->_sqlParts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias); + + $this->_aggregateAliasMap[$alias] = $sqlAlias; + $this->_queryComponents[$componentAlias]['agg'][] = $alias; + } + $this->_pendingSubqueries = array(); + } + + public function processPendingAggregates() + { + // iterate trhough all aggregates + foreach ($this->_pendingAggregates as $aggregate) { + list ($expression, $components, $alias) = $aggregate; + + $tableAliases = array(); + + // iterate through the component references within the aggregate function + if ( ! empty ($components)) { + foreach ($components as $component) { + + if (is_numeric($component)) { + continue; + } + + $e = explode('.', $component); + + $field = array_pop($e); + $componentAlias = implode('.', $e); + + // check the existence of the component alias + if ( ! isset($this->_queryComponents[$componentAlias])) { + throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias); + } + + $table = $this->_queryComponents[$componentAlias]['table']; + + $field = $table->getColumnName($field); + + // check column existence + if ( ! $table->hasColumn($field)) { + throw new IPF_ORM_Exception('Unknown column ' . $field); + } + + $sqlTableAlias = $this->getSqlTableAlias($componentAlias); + + $tableAliases[$sqlTableAlias] = true; + + // build sql expression + + $identifier = $this->_conn->quoteIdentifier($sqlTableAlias . '.' . $field); + $expression = str_replace($component, $identifier, $expression); + } + } + + if (count($tableAliases) !== 1) { + $componentAlias = reset($this->_tableAliasMap); + $tableAlias = key($this->_tableAliasMap); + } + + $index = count($this->_aggregateAliasMap); + $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index); + + $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias; + + $this->_aggregateAliasMap[$alias] = $sqlAlias; + $this->_expressionMap[$alias][0] = $expression; + + $this->_queryComponents[$componentAlias]['agg'][$index] = $alias; + + $this->_neededTables[] = $tableAlias; + } + // reset the state + $this->_pendingAggregates = array(); + } + + protected function _buildSqlQueryBase() + { + switch ($this->_type) { + case self::DELETE: + $q = 'DELETE FROM '; + break; + case self::UPDATE: + $q = 'UPDATE '; + break; + case self::SELECT: + $distinct = ($this->_sqlParts['distinct']) ? 'DISTINCT ' : ''; + $q = 'SELECT ' . $distinct . implode(', ', $this->_sqlParts['select']) . ' FROM '; + break; + } + return $q; + } + + protected function _buildSqlFromPart() + { + $q = ''; + foreach ($this->_sqlParts['from'] as $k => $part) { + if ($k === 0) { + $q .= $part; + continue; + } + + // preserve LEFT JOINs only if needed + // Check if it's JOIN, if not add a comma separator instead of space + if (!preg_match('/\bJOIN\b/i', $part) && !isset($this->_pendingJoinConditions[$k])) { + $q .= ', ' . $part; + } else { + $e = explode(' ', $part); + + if (substr($part, 0, 9) === 'LEFT JOIN') { + $aliases = array_merge($this->_subqueryAliases, + array_keys($this->_neededTables)); + + if ( ! in_array($e[3], $aliases) && + ! in_array($e[2], $aliases) && + + ! empty($this->_pendingFields)) { + continue; + } + + } + + if (isset($this->_pendingJoinConditions[$k])) { + $parser = new IPF_ORM_JoinCondition($this, $this->_tokenizer); + + if (strpos($part, ' ON ') !== false) { + $part .= ' AND '; + } else { + $part .= ' ON '; + } + $part .= $parser->parse($this->_pendingJoinConditions[$k]); + + unset($this->_pendingJoinConditions[$k]); + } + + $tableAlias = trim($e[3], '"'); + $componentAlias = $this->getComponentAlias($tableAlias); + + $string = $this->getInheritanceCondition($componentAlias); + + if ($string) { + $q .= ' ' . $part . ' AND ' . $string; + } else { + $q .= ' ' . $part; + } + } + + $this->_sqlParts['from'][$k] = $part; + } + return $q; + } + + public function getSqlQuery($params = array()) + { + if ($this->_state !== self::STATE_DIRTY) { + return $this->_sql; + } + + // reset the state + if ( ! $this->isSubquery()) { + $this->_queryComponents = array(); + $this->_pendingAggregates = array(); + $this->_aggregateAliasMap = array(); + } + $this->reset(); + + // invoke the preQuery hook + $this->_preQuery(); + + // process the DQL parts => generate the SQL parts. + // this will also populate the $_queryComponents. + foreach ($this->_dqlParts as $queryPartName => $queryParts) { + $this->_processDqlQueryPart($queryPartName, $queryParts); + } + $this->_state = self::STATE_CLEAN; + + $params = $this->convertEnums($params); + + // Proceed with the generated SQL + + if (empty($this->_sqlParts['from'])) { + return false; + } + + $map = reset($this->_queryComponents); + $table = $map['table']; + $rootAlias = key($this->_queryComponents); + + $sql = array(); + if ( ! empty($this->_pendingFields)) { + foreach ($this->_queryComponents as $alias => $map) { + $fieldSql = $this->processPendingFields($alias); + if ( ! empty($fieldSql)) { + $sql[] = $fieldSql; + } + } + } + if ( ! empty($sql)) { + array_unshift($this->_sqlParts['select'], implode(', ', $sql)); + } + + $this->_pendingFields = array(); + + // build the basic query + $q = $this->_buildSqlQueryBase(); + $q .= $this->_buildSqlFromPart(); + + if ( ! empty($this->_sqlParts['set'])) { + $q .= ' SET ' . implode(', ', $this->_sqlParts['set']); + } + + $string = $this->getInheritanceCondition($this->getRootAlias()); + + // apply inheritance to WHERE part + if ( ! empty($string)) { + if (substr($string, 0, 1) === '(' && substr($string, -1) === ')') { + $this->_sqlParts['where'][] = $string; + } else { + $this->_sqlParts['where'][] = '(' . $string . ')'; + } + } + + $q .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : ''; + $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : ''; + $q .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']): ''; + $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : ''; + + $q = $this->_conn->modifyLimitQuery($q, $this->_sqlParts['limit'], $this->_sqlParts['offset']); + + // return to the previous state + if ( ! empty($string)) { + array_pop($this->_sqlParts['where']); + } + $this->_sql = $q; + + return $q; + } + + public function parseDqlQuery($query, $clear = true) + { + if ($clear) { + $this->clear(); + } + + $query = trim($query); + $query = str_replace("\r", "\n", str_replace("\r\n", "\n", $query)); + $query = str_replace("\n", ' ', $query); + + $parts = $this->_tokenizer->tokenizeQuery($query); + + foreach ($parts as $partName => $subParts) { + $subParts = trim($subParts); + $partName = strtolower($partName); + switch ($partName) { + case 'create': + $this->_type = self::CREATE; + break; + case 'insert': + $this->_type = self::INSERT; + break; + case 'delete': + $this->_type = self::DELETE; + break; + case 'select': + $this->_type = self::SELECT; + $this->_addDqlQueryPart($partName, $subParts); + break; + case 'update': + $this->_type = self::UPDATE; + $partName = 'from'; + case 'from': + $this->_addDqlQueryPart($partName, $subParts); + break; + case 'set': + $this->_addDqlQueryPart($partName, $subParts, true); + break; + case 'group': + case 'order': + $partName .= 'by'; + case 'where': + case 'having': + case 'limit': + case 'offset': + $this->_addDqlQueryPart($partName, $subParts); + break; + } + } + + return $this; + } + + public function load($path, $loadFields = true) + { + if (isset($this->_queryComponents[$path])) { + return $this->_queryComponents[$path]; + } + + $e = $this->_tokenizer->quoteExplode($path, ' INDEXBY '); + + $mapWith = null; + if (count($e) > 1) { + $mapWith = trim($e[1]); + + $path = $e[0]; + } + + // parse custom join conditions + $e = explode(' ON ', $path); + + $joinCondition = ''; + + if (count($e) > 1) { + $joinCondition = $e[1]; + $overrideJoin = true; + $path = $e[0]; + } else { + $e = explode(' WITH ', $path); + + if (count($e) > 1) { + $joinCondition = $e[1]; + $path = $e[0]; + } + $overrideJoin = false; + } + + $tmp = explode(' ', $path); + $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null; + + $e = preg_split("/[.:]/", $tmp[0], -1); + + $fullPath = $tmp[0]; + $prevPath = ''; + $fullLength = strlen($fullPath); + + if (isset($this->_queryComponents[$e[0]])) { + $table = $this->_queryComponents[$e[0]]['table']; + $componentAlias = $e[0]; + + $prevPath = $parent = array_shift($e); + } + + foreach ($e as $key => $name) { + // get length of the previous path + $length = strlen($prevPath); + + // build the current component path + $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name; + + $delimeter = substr($fullPath, $length, 1); + + // if an alias is not given use the current path as an alias identifier + if (strlen($prevPath) === $fullLength && isset($originalAlias)) { + $componentAlias = $originalAlias; + } else { + $componentAlias = $prevPath; + } + + // if the current alias already exists, skip it + if (isset($this->_queryComponents[$componentAlias])) { + throw new IPF_ORM_Exception("Duplicate alias '$componentAlias' in query."); + } + + if ( ! isset($table)) { + // process the root of the path + + $table = $this->loadRoot($name, $componentAlias); + } else { + $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN '; + + $relation = $table->getRelation($name); + $localTable = $table; + + $table = $relation->getTable(); + $this->_queryComponents[$componentAlias] = array('table' => $table, + 'parent' => $parent, + 'relation' => $relation, + 'map' => null); + + $localAlias = $this->getTableAlias($parent, $table->getTableName()); + $foreignAlias = $this->getTableAlias($componentAlias, $relation->getTable()->getTableName()); + + $foreignSql = $this->_conn->quoteIdentifier($relation->getTable()->getTableName()) + . ' ' + . $this->_conn->quoteIdentifier($foreignAlias); + + $map = $relation->getTable()->inheritanceMap; + + if ( ! $loadFields || ! empty($map) || $joinCondition) { + $this->_subqueryAliases[] = $foreignAlias; + } + + if ($relation instanceof IPF_ORM_Relation_Association) { + $asf = $relation->getAssociationTable(); + + $assocTableName = $asf->getTableName(); + + if ( ! $loadFields || ! empty($map) || $joinCondition) { + $this->_subqueryAliases[] = $assocTableName; + } + + $assocPath = $prevPath . '.' . $asf->getComponentName(); + + $this->_queryComponents[$assocPath] = array('parent' => $prevPath, 'relation' => $relation, 'table' => $asf); + + $assocAlias = $this->getTableAlias($assocPath, $asf->getTableName()); + + $queryPart = $join . $assocTableName . ' ' . $assocAlias; + + $queryPart .= ' ON ' . $localAlias + . '.' + . $localTable->getColumnName($localTable->getIdentifier()) // what about composite keys? + . ' = ' + . $assocAlias . '.' . $relation->getLocal(); + + if ($relation->isEqual()) { + // equal nest relation needs additional condition + $queryPart .= ' OR ' . $localAlias + . '.' + . $table->getColumnName($table->getIdentifier()) + . ' = ' + . $assocAlias . '.' . $relation->getForeign(); + } + + $this->_sqlParts['from'][] = $queryPart; + + $queryPart = $join . $foreignSql; + + if ( ! $overrideJoin) { + $queryPart .= $this->buildAssociativeRelationSql($relation, $assocAlias, $foreignAlias, $localAlias); + } + } else { + $queryPart = $this->buildSimpleRelationSql($relation, $foreignAlias, $localAlias, $overrideJoin, $join); + } + + $queryPart .= $this->buildInheritanceJoinSql($table->getComponentName(), $componentAlias); + + $this->_sqlParts['from'][$componentAlias] = $queryPart; + if ( ! empty($joinCondition)) { + $this->_pendingJoinConditions[$componentAlias] = $joinCondition; + } + } + if ($loadFields) { + + $restoreState = false; + // load fields if necessary + if ($loadFields && empty($this->_dqlParts['select'])) { + $this->_pendingFields[$componentAlias] = array('*'); + } + } + $parent = $prevPath; + } + + $table = $this->_queryComponents[$componentAlias]['table']; + + return $this->buildIndexBy($componentAlias, $mapWith); + } + + protected function buildSimpleRelationSql(IPF_ORM_Relation $relation, $foreignAlias, $localAlias, $overrideJoin, $join) + { + $queryPart = $join . $this->_conn->quoteIdentifier($relation->getTable()->getTableName()) + . ' ' + . $this->_conn->quoteIdentifier($foreignAlias); + + if ( ! $overrideJoin) { + $queryPart .= ' ON ' + . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocal()) + . ' = ' + . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeign()); + } + + return $queryPart; + } + + protected function buildIndexBy($componentAlias, $mapWith = null) + { + $table = $this->_queryComponents[$componentAlias]['table']; + + $indexBy = null; + + if (isset($mapWith)) { + $terms = explode('.', $mapWith); + + if (isset($terms[1])) { + $indexBy = $terms[1]; + } + } elseif ($table->getBoundQueryPart('indexBy') !== null) { + $indexBy = $table->getBoundQueryPart('indexBy'); + } + + if ($indexBy !== null) { + if ( ! $table->hasColumn($table->getColumnName($indexBy))) { + throw new IPF_ORM_Exception("Couldn't use key mapping. Column " . $indexBy . " does not exist."); + } + + $this->_queryComponents[$componentAlias]['map'] = $indexBy; + } + + return $this->_queryComponents[$componentAlias]; + } + + + protected function buildAssociativeRelationSql(IPF_ORM_Relation $relation, $assocAlias, $foreignAlias, $localAlias) + { + $table = $relation->getTable(); + + $queryPart = ' ON '; + + if ($relation->isEqual()) { + $queryPart .= '('; + } + + $localIdentifier = $table->getColumnName($table->getIdentifier()); + + $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier) + . ' = ' + . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeign()); + + if ($relation->isEqual()) { + $queryPart .= ' OR ' + . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier) + . ' = ' + . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocal()) + . ') AND ' + . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier) + . ' != ' + . $this->_conn->quoteIdentifier($localAlias . '.' . $localIdentifier); + } + + return $queryPart; + } + + public function loadRoot($name, $componentAlias) + { + // get the connection for the component + $manager = IPF_ORM_Manager::getInstance(); + + $table = $this->_conn->getTable($name); + $tableName = $table->getTableName(); + + // get the short alias for this table + $tableAlias = $this->getTableAlias($componentAlias, $tableName); + // quote table name + $queryPart = $this->_conn->quoteIdentifier($tableName); + + if ($this->_type === self::SELECT) { + $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias); + } + + $this->_tableAliasMap[$tableAlias] = $componentAlias; + + $queryPart .= $this->buildInheritanceJoinSql($name, $componentAlias); + + $this->_sqlParts['from'][] = $queryPart; + + $this->_queryComponents[$componentAlias] = array('table' => $table, 'map' => null); + + return $table; + } + + public function buildInheritanceJoinSql($name, $componentAlias) + { + // get the connection for the component + $manager = IPF_ORM_Manager::getInstance(); + + $table = $this->_conn->getTable($name); + $tableName = $table->getTableName(); + + // get the short alias for this table + $tableAlias = $this->getTableAlias($componentAlias, $tableName); + + return ''; + } + + public function getCountQuery() + { + // triggers dql parsing/processing + $this->getQuery(); // this is ugly + + // initialize temporary variables + $where = $this->_sqlParts['where']; + $having = $this->_sqlParts['having']; + $groupby = $this->_sqlParts['groupby']; + $map = reset($this->_queryComponents); + $componentAlias = key($this->_queryComponents); + $tableAlias = $this->_conn->quoteIdentifier($this->getTableAlias($componentAlias)); + $table = $map['table']; + + $idColumnNames = array(); + foreach ($table->getIdentifierColumnNames() as $column) { + $idColumnNames[] = $tableAlias . '.' . $this->_conn->quoteIdentifier($column); + } + + // build the query base + $q = 'SELECT COUNT(DISTINCT ' . implode(' || ', $idColumnNames) . ') AS num_results'; + + foreach ($this->_sqlParts['select'] as $field) { + if (strpos($field, '(') !== false) { + $q .= ', ' . $field; + } + } + + $q .= ' FROM ' . $this->_buildSqlFromPart(); + + // append column aggregation inheritance (if needed) + $string = $this->getInheritanceCondition($this->getRootAlias()); + + if ( ! empty($string)) { + $where[] = $string; + } + + // append conditions + $q .= ( ! empty($where)) ? ' WHERE ' . implode(' AND ', $where) : ''; + + if ( ! empty($groupby)) { + // Maintain existing groupby + $q .= ' GROUP BY ' . implode(', ', $groupby); + } else { + // Default groupby to primary identifier. Database defaults to this internally + // This is required for situations where the user has aggregate functions in the select part + // Without the groupby it fails + $q .= ' GROUP BY ' . implode(', ', $idColumnNames); + } + + $q .= ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having): ''; + + return $q; + } + + public function count($params = array()) + { + $q = $this->getCountQuery(); + + if ( ! is_array($params)) { + $params = array($params); + } + + $params = array_merge($this->_params['join'], $this->_params['where'], $this->_params['having'], $params); + + $params = $this->convertEnums($params); + + $results = $this->getConnection()->fetchAll($q, $params); + + if (count($results) > 1) { + $count = count($results); + } else { + if (isset($results[0])) { + $results[0] = array_change_key_case($results[0], CASE_LOWER); + $count = $results[0]['num_results']; + } else { + $count = 0; + } + } + + return (int) $count; + } + + public function query($query, $params = array(), $hydrationMode = null) + { + $this->parseDqlQuery($query); + return $this->execute($params, $hydrationMode); + } + + public function copy(IPF_ORM_Query $query = null) + { + if ( ! $query) { + $query = $this; + } + + $new = clone $query; + + return $new; + } + + public function __clone() + { + $this->_parsers = array(); + } + + public function free() + { + $this->reset(); + $this->_parsers = array(); + $this->_dqlParts = array(); + $this->_enumParams = array(); + } + + public function serialize() + { + $vars = get_object_vars($this); + } + + public function unserialize($serialized) + { + } +} diff --git a/src/orm/query/abstract.php b/src/orm/query/abstract.php new file mode 100644 index 0000000..6e0f48a --- /dev/null +++ b/src/orm/query/abstract.php @@ -0,0 +1,998 @@ + array(), + 'where' => array(), + 'set' => array(), + 'having' => array()); + + protected $_resultCache; + protected $_expireResultCache = false; + protected $_resultCacheTTL; + + protected $_queryCache; + protected $_expireQueryCache = false; + protected $_queryCacheTTL; + + protected $_conn; + + protected $_sqlParts = array( + 'select' => array(), + 'distinct' => false, + 'forUpdate' => false, + 'from' => array(), + 'set' => array(), + 'join' => array(), + 'where' => array(), + 'groupby' => array(), + 'having' => array(), + 'orderby' => array(), + 'limit' => false, + 'offset' => false, + ); + + protected $_dqlParts = array( + 'from' => array(), + 'select' => array(), + 'forUpdate' => false, + 'set' => array(), + 'join' => array(), + 'where' => array(), + 'groupby' => array(), + 'having' => array(), + 'orderby' => array(), + 'limit' => array(), + 'offset' => array(), + ); + + protected $_queryComponents = array(); + protected $_type = self::SELECT; + protected $_hydrator; + protected $_tokenizer; + protected $_parser; + protected $_tableAliasSeeds = array(); + protected $_options = array( + 'fetchMode' => IPF_ORM::FETCH_RECORD + ); + protected $_enumParams = array(); + + protected $_pendingSetParams = array(); + protected $_components; + protected $_preQueried = false; + + public function __construct(IPF_ORM_Connection $connection = null, + IPF_ORM_Hydrator_Abstract $hydrator = null) + { + if ($connection === null) { + $connection = IPF_ORM_Manager::connection(); + } + if ($hydrator === null) { + $hydrator = new IPF_ORM_Hydrator(); + } + $this->_conn = $connection; + $this->_hydrator = $hydrator; + $this->_tokenizer = new IPF_ORM_Query_Tokenizer(); + $this->_resultCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_RESULT_CACHE_LIFESPAN); + $this->_queryCacheTTL = $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE_LIFESPAN); + } + + public function setOption($name, $value) + { + if ( ! isset($this->_options[$name])) { + throw new IPF_ORM_Exception('Unknown option ' . $name); + } + $this->_options[$name] = $value; + } + + public function hasTableAlias($sqlTableAlias) + { + return $this->hasSqlTableAlias($sqlTableAlias); + } + + public function hasSqlTableAlias($sqlTableAlias) + { + return (isset($this->_tableAliasMap[$sqlTableAlias])); + } + + public function getTableAliases() + { + return $this->getTableAliasMap(); + } + + public function getTableAliasMap() + { + return $this->_tableAliasMap; + } + + public function getQueryPart($part) + { + return $this->getSqlQueryPart($part); + } + + public function getSqlQueryPart($part) + { + if ( ! isset($this->_sqlParts[$part])) { + throw new IPF_ORM_Exception('Unknown SQL query part ' . $part); + } + return $this->_sqlParts[$part]; + } + + public function setQueryPart($name, $part) + { + return $this->setSqlQueryPart($name, $part); + } + + public function setSqlQueryPart($name, $part) + { + if ( ! isset($this->_sqlParts[$name])) { + throw new IPF_ORM_Exception('Unknown query part ' . $name); + } + + if ($name !== 'limit' && $name !== 'offset') { + if (is_array($part)) { + $this->_sqlParts[$name] = $part; + } else { + $this->_sqlParts[$name] = array($part); + } + } else { + $this->_sqlParts[$name] = $part; + } + + return $this; + } + + public function addQueryPart($name, $part) + { + return $this->addSqlQueryPart($name, $part); + } + + public function addSqlQueryPart($name, $part) + { + if ( ! isset($this->_sqlParts[$name])) { + throw new IPF_ORM_Exception('Unknown query part ' . $name); + } + if (is_array($part)) { + $this->_sqlParts[$name] = array_merge($this->_sqlParts[$name], $part); + } else { + $this->_sqlParts[$name][] = $part; + } + return $this; + } + + public function removeQueryPart($name) + { + return $this->removeSqlQueryPart($name); + } + + public function removeSqlQueryPart($name) + { + if ( ! isset($this->_sqlParts[$name])) { + throw new IPF_ORM_Exception('Unknown query part ' . $name); + } + + if ($name == 'limit' || $name == 'offset') { + $this->_sqlParts[$name] = false; + } else { + $this->_sqlParts[$name] = array(); + } + + return $this; + } + + public function removeDqlQueryPart($name) + { + if ( ! isset($this->_dqlParts[$name])) { + throw new IPF_ORM_Exception('Unknown query part ' . $name); + } + + if ($name == 'limit' || $name == 'offset') { + $this->_dqlParts[$name] = false; + } else { + $this->_dqlParts[$name] = array(); + } + + return $this; + } + + public function getParams($params = array()) + { + return array_merge($this->_params['join'], $this->_params['set'], $this->_params['where'], $this->_params['having'], $params); + } + + public function setParams(array $params = array()) { + $this->_params = $params; + } + + public function setView(IPF_ORM_View $view) + { + $this->_view = $view; + } + + public function getView() + { + return $this->_view; + } + + public function convertEnums($params) + { + $table = $this->getRoot(); + + // $position tracks the position of the parameter, to ensure we're converting + // the right parameter value when simple ? placeholders are used. + // This only works because SET is only allowed in update statements and it's + // the first place where parameters can occur.. see issue #935 + $position = 0; + foreach ($this->_pendingSetParams as $fieldName => $value) { + $e = explode('.', $fieldName); + $fieldName = isset($e[1]) ? $e[1]:$e[0]; + if ($table->getTypeOf($fieldName) == 'enum') { + $value = $value === '?' ? $position : $value; + $this->addEnumParam($value, $table, $fieldName); + } + ++$position; + } + $this->_pendingSetParams = array(); + + foreach ($this->_enumParams as $key => $values) { + if (isset($params[$key])) { + if ( ! empty($values)) { + $params[$key] = $values[0]->enumIndex($values[1], $params[$key]); + } + } + } + + return $params; + } + + public function getInheritanceCondition($componentAlias) + { + $map = $this->_queryComponents[$componentAlias]['table']->inheritanceMap; + + // No inheritance map so lets just return + if (empty($map)) { + return; + } + + $tableAlias = $this->getSqlTableAlias($componentAlias); + + if ($this->_type !== IPF_ORM_Query::SELECT) { + $tableAlias = ''; + } else { + $tableAlias .= '.'; + } + + $field = key($map); + $value = current($map); + $identifier = $this->_conn->quoteIdentifier($tableAlias . $field); + + return $identifier . ' = ' . $this->_conn->quote($value);; + } + + public function getTableAlias($componentAlias, $tableName = null) + { + return $this->getSqlTableAlias($componentAlias, $tableName); + } + + public function getSqlTableAlias($componentAlias, $tableName = null) + { + $alias = array_search($componentAlias, $this->_tableAliasMap); + + if ($alias !== false) { + return $alias; + } + + if ($tableName === null) { + throw new IPF_ORM_Exception("Couldn't get short alias for " . $componentAlias); + } + + return $this->generateTableAlias($componentAlias, $tableName); + } + + public function generateNewTableAlias($oldAlias) + { + return $this->generateNewSqlTableAlias($oldAlias); + } + + public function generateNewSqlTableAlias($oldAlias) + { + if (isset($this->_tableAliasMap[$oldAlias])) { + // generate a new alias + $name = substr($oldAlias, 0, 1); + $i = ((int) substr($oldAlias, 1)); + + if ($i == 0) { + $i = 1; + } + + $newIndex = ($this->_tableAliasSeeds[$name] + $i); + + return $name . $newIndex; + } + + return $oldAlias; + } + + public function getTableAliasSeed($sqlTableAlias) + { + return $this->getSqlTableAliasSeed($sqlTableAlias); + } + + public function getSqlTableAliasSeed($sqlTableAlias) + { + if ( ! isset($this->_tableAliasSeeds[$sqlTableAlias])) { + return 0; + } + return $this->_tableAliasSeeds[$sqlTableAlias]; + } + + public function hasAliasDeclaration($componentAlias) + { + return isset($this->_queryComponents[$componentAlias]); + } + + public function getAliasDeclaration($componentAlias) + { + return $this->getQueryComponent($componentAlias); + } + + public function getQueryComponent($componentAlias) + { + if ( ! isset($this->_queryComponents[$componentAlias])) { + throw new IPF_ORM_Exception('Unknown component alias ' . $componentAlias); + } + + return $this->_queryComponents[$componentAlias]; + } + + public function copyAliases(IPF_ORM_Query_Abstract $query) + { + $this->_tableAliasMap = $query->_tableAliasMap; + $this->_queryComponents = $query->_queryComponents; + $this->_tableAliasSeeds = $query->_tableAliasSeeds; + return $this; + } + + public function getRootAlias() + { + if ( ! $this->_queryComponents) { + $this->getSql(); + } + reset($this->_queryComponents); + + return key($this->_queryComponents); + } + + public function getRootDeclaration() + { + $map = reset($this->_queryComponents); + return $map; + } + + public function getRoot() + { + $map = reset($this->_queryComponents); + + if ( ! isset($map['table'])) { + throw new IPF_ORM_Exception('Root component not initialized.'); + } + + return $map['table']; + } + + public function generateTableAlias($componentAlias, $tableName) + { + return $this->generateSqlTableAlias($componentAlias, $tableName); + } + + public function generateSqlTableAlias($componentAlias, $tableName) + { + preg_match('/([^_])/', $tableName, $matches); + $char = strtolower($matches[0]); + + $alias = $char; + + if ( ! isset($this->_tableAliasSeeds[$alias])) { + $this->_tableAliasSeeds[$alias] = 1; + } + + while (isset($this->_tableAliasMap[$alias])) { + if ( ! isset($this->_tableAliasSeeds[$alias])) { + $this->_tableAliasSeeds[$alias] = 1; + } + $alias = $char . ++$this->_tableAliasSeeds[$alias]; + } + + $this->_tableAliasMap[$alias] = $componentAlias; + + return $alias; + } + + public function getComponentAlias($sqlTableAlias) + { + if ( ! isset($this->_tableAliasMap[$sqlTableAlias])) { + throw new IPF_ORM_Exception('Unknown table alias ' . $sqlTableAlias); + } + return $this->_tableAliasMap[$sqlTableAlias]; + } + + protected function _execute($params) + { + $params = $this->_conn->convertBooleans($params); + + if ( ! $this->_view) { + if ($this->_queryCache || $this->_conn->getAttribute(IPF_ORM::ATTR_QUERY_CACHE)) { + $queryCacheDriver = $this->getQueryCacheDriver(); + // calculate hash for dql query + $dql = $this->getDql(); + $hash = md5($dql . 'IPF_ORM_QUERY_CACHE_SALT'); + $cached = $queryCacheDriver->fetch($hash); + if ($cached) { + $query = $this->_constructQueryFromCache($cached); + } else { + $query = $this->getSqlQuery($params); + $serializedQuery = $this->getCachedForm($query); + $queryCacheDriver->save($hash, $serializedQuery, $this->getQueryCacheLifeSpan()); + } + } else { + $query = $this->getSqlQuery($params); + } + $params = $this->convertEnums($params); + } else { + $query = $this->_view->getSelectSql(); + } + + if ($this->_type !== self::SELECT) { + return $this->_conn->exec($query, $params); + } + + $stmt = $this->_conn->execute($query, $params); + return $stmt; + } + + public function execute($params = array(), $hydrationMode = null) + { + $this->_preQuery(); + + if ($hydrationMode !== null) { + $this->_hydrator->setHydrationMode($hydrationMode); + } + + $params = $this->getParams($params); + + if ($this->_resultCache && $this->_type == self::SELECT) { + $cacheDriver = $this->getResultCacheDriver(); + + $dql = $this->getDql(); + // calculate hash for dql query + $hash = md5($dql . var_export($params, true)); + + $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash); + + if ($cached === false) { + // cache miss + $stmt = $this->_execute($params); + $this->_hydrator->setQueryComponents($this->_queryComponents); + $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap); + + $cached = $this->getCachedForm($result); + $cacheDriver->save($hash, $cached, $this->getResultCacheLifeSpan()); + } else { + $result = $this->_constructQueryFromCache($cached); + } + } else { + $stmt = $this->_execute($params); + + if (is_integer($stmt)) { + $result = $stmt; + } else { + $this->_hydrator->setQueryComponents($this->_queryComponents); + $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap); + } + } + + return $result; + } + + protected function _preQuery() + { + // Invoke preQuery() hook on IPF_ORM_Query for child classes which implement this hook + $this->preQuery(); + } + + public function preQuery() + { + } + + protected function _constructQueryFromCache($cached) + { + $cached = unserialize($cached); + $this->_tableAliasMap = $cached[2]; + $customComponent = $cached[0]; + + $queryComponents = array(); + $cachedComponents = $cached[1]; + foreach ($cachedComponents as $alias => $components) { + $e = explode('.', $components[0]); + if (count($e) === 1) { + $queryComponents[$alias]['table'] = $this->_conn->getTable($e[0]); + } else { + $queryComponents[$alias]['parent'] = $e[0]; + $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]); + $queryComponents[$alias]['table'] = $queryComponents[$alias]['relation']->getTable(); + } + if (isset($components[1])) { + $queryComponents[$alias]['agg'] = $components[1]; + } + if (isset($components[2])) { + $queryComponents[$alias]['map'] = $components[2]; + } + } + $this->_queryComponents = $queryComponents; + + return $customComponent; + } + + public function getCachedForm($customComponent = null) + { + $componentInfo = array(); + + foreach ($this->getQueryComponents() as $alias => $components) { + if ( ! isset($components['parent'])) { + $componentInfo[$alias][] = $components['table']->getComponentName(); + } else { + $componentInfo[$alias][] = $components['parent'] . '.' . $components['relation']->getAlias(); + } + if (isset($components['agg'])) { + $componentInfo[$alias][] = $components['agg']; + } + if (isset($components['map'])) { + $componentInfo[$alias][] = $components['map']; + } + } + + return serialize(array($customComponent, $componentInfo, $this->getTableAliasMap())); + } + + public function addSelect($select) + { + return $this->_addDqlQueryPart('select', $select, true); + } + + public function addTableAlias($tableAlias, $componentAlias) + { + return $this->addSqlTableAlias($tableAlias, $componentAlias); + } + + public function addSqlTableAlias($sqlTableAlias, $componentAlias) + { + $this->_tableAliasMap[$sqlTableAlias] = $componentAlias; + return $this; + } + + public function addFrom($from) + { + return $this->_addDqlQueryPart('from', $from, true); + } + + public function addWhere($where, $params = array()) + { + if (is_array($params)) { + $this->_params['where'] = array_merge($this->_params['where'], $params); + } else { + $this->_params['where'][] = $params; + } + return $this->_addDqlQueryPart('where', $where, true); + } + + public function whereIn($expr, $params = array(), $not = false) + { + $params = (array) $params; + + // if there's no params, return (else we'll get a WHERE IN (), invalid SQL) + if (!count($params)) + return $this; + + $a = array(); + foreach ($params as $k => $value) { + if ($value instanceof IPF_ORM_Expression) { + $value = $value->getSql(); + unset($params[$k]); + } else { + $value = '?'; + } + $a[] = $value; + } + + $this->_params['where'] = array_merge($this->_params['where'], $params); + + $where = $expr . ($not === true ? ' NOT ':'') . ' IN (' . implode(', ', $a) . ')'; + + return $this->_addDqlQueryPart('where', $where, true); + } + + public function whereNotIn($expr, $params = array()) + { + return $this->whereIn($expr, $params, true); + } + + public function addGroupBy($groupby) + { + return $this->_addDqlQueryPart('groupby', $groupby, true); + } + + public function addHaving($having, $params = array()) + { + if (is_array($params)) { + $this->_params['having'] = array_merge($this->_params['having'], $params); + } else { + $this->_params['having'][] = $params; + } + return $this->_addDqlQueryPart('having', $having, true); + } + + public function addOrderBy($orderby) + { + return $this->_addDqlQueryPart('orderby', $orderby, true); + } + + public function select($select) + { + return $this->_addDqlQueryPart('select', $select); + } + + public function distinct($flag = true) + { + $this->_sqlParts['distinct'] = (bool) $flag; + return $this; + } + + public function forUpdate($flag = true) + { + $this->_sqlParts['forUpdate'] = (bool) $flag; + return $this; + } + + public function delete() + { + $this->_type = self::DELETE; + return $this; + } + + public function update($update) + { + $this->_type = self::UPDATE; + return $this->_addDqlQueryPart('from', $update); + } + + public function set($key, $value, $params = null) + { + if (is_array($key)) { + foreach ($key as $k => $v) { + $this->set($k, '?', array($v)); + } + return $this; + } else { + if ($params !== null) { + if (is_array($params)) { + $this->_params['set'] = array_merge($this->_params['set'], $params); + } else { + $this->_params['set'][] = $params; + } + } + + $this->_pendingSetParams[$key] = $value; + + return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true); + } + } + + public function from($from) + { + return $this->_addDqlQueryPart('from', $from); + } + + public function innerJoin($join, $params = array()) + { + if (is_array($params)) { + $this->_params['join'] = array_merge($this->_params['join'], $params); + } else { + $this->_params['join'][] = $params; + } + + return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true); + } + + public function leftJoin($join, $params = array()) + { + if (is_array($params)) { + $this->_params['join'] = array_merge($this->_params['join'], $params); + } else { + $this->_params['join'][] = $params; + } + + return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true); + } + + public function groupBy($groupby) + { + return $this->_addDqlQueryPart('groupby', $groupby); + } + + public function where($where, $params = array()) + { + $this->_params['where'] = array(); + if (is_array($params)) { + $this->_params['where'] = $params; + } else { + $this->_params['where'][] = $params; + } + + return $this->_addDqlQueryPart('where', $where); + } + + public function having($having, $params = array()) + { + $this->_params['having'] = array(); + if (is_array($params)) { + $this->_params['having'] = $params; + } else { + $this->_params['having'][] = $params; + } + + return $this->_addDqlQueryPart('having', $having); + } + + public function orderBy($orderby) + { + return $this->_addDqlQueryPart('orderby', $orderby); + } + + public function limit($limit) + { + return $this->_addDqlQueryPart('limit', $limit); + } + + public function offset($offset) + { + return $this->_addDqlQueryPart('offset', $offset); + } + + public function getSql($params = array()) + { + return $this->getSqlQuery($params); + } + + protected function clear() + { + $this->_sqlParts = array( + 'select' => array(), + 'distinct' => false, + 'forUpdate' => false, + 'from' => array(), + 'set' => array(), + 'join' => array(), + 'where' => array(), + 'groupby' => array(), + 'having' => array(), + 'orderby' => array(), + 'limit' => false, + 'offset' => false, + ); + } + + public function setHydrationMode($hydrationMode) + { + $this->_hydrator->setHydrationMode($hydrationMode); + return $this; + } + + public function getAliasMap() + { + return $this->_queryComponents; + } + + public function getQueryComponents() + { + return $this->_queryComponents; + } + + public function getParts() + { + return $this->getSqlParts(); + } + + public function getSqlParts() + { + return $this->_sqlParts; + } + + public function getType() + { + return $this->_type; + } + + public function useCache($driver = true, $timeToLive = null) + { + return $this->useResultCache($driver, $timeToLive); + } + + public function useResultCache($driver = true, $timeToLive = null) + { + if ($driver !== null && $driver !== true && ! ($driver instanceOf IPF_ORM_Cache_Interface)) { + $msg = 'First argument should be instance of IPF_ORM_Cache_Interface or null.'; + throw new IPF_ORM_Exception($msg); + } + $this->_resultCache = $driver; + + return $this->setResultCacheLifeSpan($timeToLive); + } + + public function useQueryCache(IPF_ORM_Cache_Interface $driver, $timeToLive = null) + { + $this->_queryCache = $driver; + return $this->setQueryCacheLifeSpan($timeToLive); + } + + public function expireCache($expire = true) + { + return $this->expireResultCache($expire); + } + + public function expireResultCache($expire = true) + { + $this->_expireResultCache = true; + return $this; + } + + public function expireQueryCache($expire = true) + { + $this->_expireQueryCache = true; + return $this; + } + + public function setCacheLifeSpan($timeToLive) + { + return $this->setResultCacheLifeSpan($timeToLive); + } + + public function setResultCacheLifeSpan($timeToLive) + { + if ($timeToLive !== null) { + $timeToLive = (int) $timeToLive; + } + $this->_resultCacheTTL = $timeToLive; + + return $this; + } + + public function getResultCacheLifeSpan() + { + return $this->_resultCacheTTL; + } + + public function setQueryCacheLifeSpan($timeToLive) + { + if ($timeToLive !== null) { + $timeToLive = (int) $timeToLive; + } + $this->_queryCacheTTL = $timeToLive; + + return $this; + } + + public function getQueryCacheLifeSpan() + { + return $this->_queryCacheTTL; + } + + public function getCacheDriver() + { + return $this->getResultCacheDriver(); + } + + public function getResultCacheDriver() + { + if ($this->_resultCache instanceof IPF_ORM_Cache_Interface) { + return $this->_resultCache; + } else { + return $this->_conn->getResultCacheDriver(); + } + } + + public function getQueryCacheDriver() + { + if ($this->_queryCache instanceof IPF_ORM_Cache_Interface) { + return $this->_queryCache; + } else { + return $this->_conn->getQueryCacheDriver(); + } + } + + public function getConnection() + { + return $this->_conn; + } + + protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false) + { + if ($append) { + $this->_dqlParts[$queryPartName][] = $queryPart; + } else { + $this->_dqlParts[$queryPartName] = array($queryPart); + } + + $this->_state = IPF_ORM_Query::STATE_DIRTY; + return $this; + } + + protected function _processDqlQueryPart($queryPartName, $queryParts) + { + $this->removeSqlQueryPart($queryPartName); + + if (is_array($queryParts) && ! empty($queryParts)) { + foreach ($queryParts as $queryPart) { + $parser = $this->_getParser($queryPartName); + $sql = $parser->parse($queryPart); + if (isset($sql)) { + if ($queryPartName == 'limit' || $queryPartName == 'offset') { + $this->setSqlQueryPart($queryPartName, $sql); + } else { + $this->addSqlQueryPart($queryPartName, $sql); + } + } + } + } + } + + protected function _getParser($name) + { + if ( ! isset($this->_parsers[$name])) { + $class = 'IPF_ORM_Query_' . ucwords(strtolower($name)); + + //IPF_ORM::autoload($class); + + if ( ! class_exists($class)) { + throw new IPF_ORM_Exception('Unknown parser ' . $name); + } + + $this->_parsers[$name] = new $class($this, $this->_tokenizer); + } + + return $this->_parsers[$name]; + } + + abstract public function getSqlQuery($params = array()); + + abstract public function parseDqlQuery($query); + + public function parseQuery($query) + { + return $this->parseDqlQuery($query); + } + + public function getQuery($params = array()) + { + return $this->getSqlQuery($params); + } + + public function __debugInfo() + { + return array('sql' => $this->getQuery()); + } +} + diff --git a/src/orm/query/check.php b/src/orm/query/check.php new file mode 100644 index 0000000..79728c3 --- /dev/null +++ b/src/orm/query/check.php @@ -0,0 +1,99 @@ +getTable($table); + } + $this->table = $table; + $this->_tokenizer = new IPF_ORM_Query_Tokenizer(); + } + + public function getTable() + { + return $this->table; + } + + public function parse($dql) + { + $this->sql = $this->parseClause($dql); + } + + public function parseClause($dql) + { + $parts = $this->_tokenizer->sqlExplode($dql, ' AND '); + + if (count($parts) > 1) { + $ret = array(); + foreach ($parts as $part) { + $ret[] = $this->parseSingle($part); + } + + $r = implode(' AND ', $ret); + } else { + $parts = $this->_tokenizer->quoteExplode($dql, ' OR '); + if (count($parts) > 1) { + $ret = array(); + foreach ($parts as $part) { + $ret[] = $this->parseClause($part); + } + + $r = implode(' OR ', $ret); + } else { + $ret = $this->parseSingle($dql); + return $ret; + } + } + return '(' . $r . ')'; + } + + public function parseSingle($part) + { + $e = explode(' ', $part); + + $e[0] = $this->parseFunction($e[0]); + + switch ($e[1]) { + case '>': + case '<': + case '=': + case '!=': + case '<>': + + break; + default: + throw new IPF_ORM_Exception('Unknown operator ' . $e[1]); + } + + return implode(' ', $e); + } + public function parseFunction($dql) + { + if (($pos = strpos($dql, '(')) !== false) { + $func = substr($dql, 0, $pos); + $value = substr($dql, ($pos + 1), -1); + + $expr = $this->table->getConnection()->expression; + + if ( ! method_exists($expr, $func)) { + throw new IPF_ORM_Exception('Unknown function ' . $func); + } + + $func = $expr->$func($value); + } + return $func; + } + + public function getSql() + { + return $this->sql; + } +} diff --git a/src/orm/query/condition.php b/src/orm/query/condition.php new file mode 100644 index 0000000..b18e9eb --- /dev/null +++ b/src/orm/query/condition.php @@ -0,0 +1,81 @@ +_tokenizer->bracketExplode($str, array(' \&\& ', ' AND '), '(', ')'); + + if (count($parts) > 1) { + $ret = array(); + foreach ($parts as $part) { + $part = $this->_tokenizer->bracketTrim($part, '(', ')'); + $ret[] = $this->parse($part); + } + $r = implode(' AND ', $ret); + } else { + + $parts = $this->_tokenizer->bracketExplode($str, array(' \|\| ', ' OR '), '(', ')'); + if (count($parts) > 1) { + $ret = array(); + foreach ($parts as $part) { + $part = $this->_tokenizer->bracketTrim($part, '(', ')'); + $ret[] = $this->parse($part); + } + $r = implode(' OR ', $ret); + } else { + // Fix for #710 + if (substr($parts[0],0,1) == '(' && substr($parts[0], -1) == ')') { + return $this->parse(substr($parts[0], 1, -1)); + } else { + // Processing NOT here + if (strtoupper(substr($parts[0], 0, 4)) === 'NOT ') { + $r = 'NOT ('.$this->parse(substr($parts[0], 4)).')'; + } else { + return $this->load($parts[0]); + } + } + } + } + + return '(' . $r . ')'; + } + + private function parseBoolean($value) + { + // parse booleans + if ($value == 'true') { + $value = 1; + } elseif ($value == 'false') { + $value = 0; + } + return $value; + } + + public function parseLiteralValue($value) + { + // check that value isn't a string + if (strpos($value, '\'') === false) { + // parse booleans + $value = $this->parseBoolean($value); + + $a = explode('.', $value); + + if (count($a) > 1) { + // either a float or a component.. + + if ( ! is_numeric($a[0])) { + // a component found + $field = array_pop($a); + $reference = implode('.', $a); + $value = $this->query->getTableAlias($reference). '.' . $field; + } + } + } else { + // string literal found + } + return $value; + } +} diff --git a/src/orm/query/filter.php b/src/orm/query/filter.php new file mode 100644 index 0000000..65b0917 --- /dev/null +++ b/src/orm/query/filter.php @@ -0,0 +1,12 @@ +_filters[] = $filter; + } + + public function get($key) + { + if ( ! isset($this->_filters[$key])) { + throw new IPF_ORM_Exception('Unknown filter ' . $key); + } + return $this->_filters[$key]; + } + + public function set($key, IPF_ORM_Query_Filter $listener) + { + $this->_filters[$key] = $listener; + } + + public function preQuery(IPF_ORM_Query $query) + { + foreach ($this->_filters as $filter) { + $filter->preQuery($query); + } + } + + public function postQuery(IPF_ORM_Query $query) + { + foreach ($this->_filters as $filter) { + $filter->postQuery($query); + } + } +} \ No newline at end of file diff --git a/src/orm/query/filter/interface.php b/src/orm/query/filter/interface.php new file mode 100644 index 0000000..09c104f --- /dev/null +++ b/src/orm/query/filter/interface.php @@ -0,0 +1,7 @@ +_tokenizer->bracketExplode($str, 'JOIN'); + + $from = $return ? array() : null; + + $operator = false; + + switch (trim($parts[0])) { + case 'INNER': + $operator = ':'; + case 'LEFT': + array_shift($parts); + break; + } + + $last = ''; + + foreach ($parts as $k => $part) { + $part = trim($part); + + if (empty($part)) { + continue; + } + + $e = explode(' ', $part); + + if (end($e) == 'INNER' || end($e) == 'LEFT') { + $last = array_pop($e); + } + $part = implode(' ', $e); + + foreach ($this->_tokenizer->bracketExplode($part, ',') as $reference) { + $reference = trim($reference); + $e = explode(' ', $reference); + $e2 = explode('.', $e[0]); + + if ($operator) { + $e[0] = array_shift($e2) . $operator . implode('.', $e2); + } + + if ($return) { + $from[] = $e; + } else { + $table = $this->query->load(implode(' ', $e)); + } + } + + $operator = ($last == 'INNER') ? ':' : '.'; + } + return $from; + } +} diff --git a/src/orm/query/groupby.php b/src/orm/query/groupby.php new file mode 100644 index 0000000..824f11c --- /dev/null +++ b/src/orm/query/groupby.php @@ -0,0 +1,15 @@ +query->parseClause($reference); + } + return implode(', ', $r); + } +} diff --git a/src/orm/query/having.php b/src/orm/query/having.php new file mode 100644 index 0000000..3fe3b6d --- /dev/null +++ b/src/orm/query/having.php @@ -0,0 +1,58 @@ +_tokenizer->bracketExplode($func, ',', '(', ')'); + + foreach ($params as $k => $param) { + $params[$k] = $this->parseAggregateFunction($param); + } + + $funcs = $name . '(' . implode(', ', $params) . ')'; + + return $funcs; + + } else { + if ( ! is_numeric($func)) { + $a = explode('.', $func); + + if (count($a) > 1) { + $field = array_pop($a); + $reference = implode('.', $a); + $map = $this->query->load($reference, false); + $field = $map['table']->getColumnName($field); + $func = $this->query->getTableAlias($reference) . '.' . $field; + } else { + $field = end($a); + $func = $this->query->getAggregateAlias($field); + } + return $func; + } else { + return $func; + } + } + } + + final public function load($having) + { + $tokens = $this->_tokenizer->bracketExplode($having, ' ', '(', ')'); + $part = $this->parseAggregateFunction(array_shift($tokens)); + $operator = array_shift($tokens); + $value = implode(' ', $tokens); + $part .= ' ' . $operator . ' ' . $value; + // check the RHS for aggregate functions + if (strpos($value, '(') !== false) { + $value = $this->parseAggregateFunction($value); + } + return $part; + } +} diff --git a/src/orm/query/joincondition.php b/src/orm/query/joincondition.php new file mode 100644 index 0000000..94d969b --- /dev/null +++ b/src/orm/query/joincondition.php @@ -0,0 +1,126 @@ +_tokenizer->sqlExplode($condition); + + if (count($e) > 2) { + $expr = new IPF_ORM_Expression($e[0], $this->query->getConnection()); + $e[0] = $expr->getSql(); + + $operator = $e[1]; + + if (substr(trim($e[2]), 0, 1) != '(') { + $expr = new IPF_ORM_Expression($e[2], $this->query->getConnection()); + $e[2] = $expr->getSql(); + } + + // We need to check for agg functions here + $hasLeftAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[0], $leftMatches); + + if ($hasLeftAggExpression) { + $e[0] = $leftMatches[2]; + } + + $hasRightAggExpression = preg_match('/(.*)\(([^\)]*)\)([\)]*)/', $e[2], $rightMatches); + + if ($hasRightAggExpression) { + $e[2] = $rightMatches[2]; + } + + $a = explode('.', $e[0]); + $field = array_pop($a); + $reference = implode('.', $a); + $value = $e[2]; + + $conn = $this->query->getConnection(); + $alias = $this->query->getTableAlias($reference); + $map = $this->query->getAliasDeclaration($reference); + $table = $map['table']; + // check if value is enumerated value + $enumIndex = $table->enumIndex($field, trim($value, "'")); + + if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { + $enumIndex = $conn->quote($enumIndex, 'text'); + } + + // FIX: Issues with "(" XXX ")" + if ($hasRightAggExpression) { + $value = '(' . $value . ')'; + } + + if (substr($value, 0, 1) == '(') { + // trim brackets + $trimmed = $this->_tokenizer->bracketTrim($value); + + if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') { + // subquery found + $q = $this->query->createSubquery(); + + // Change due to bug "(" XXX ")" + //$value = '(' . $q->parseQuery($trimmed)->getQuery() . ')'; + $value = $q->parseQuery($trimmed)->getQuery(); + } elseif (substr($trimmed, 0, 4) == 'SQL:') { + // Change due to bug "(" XXX ")" + //$value = '(' . substr($trimmed, 4) . ')'; + $value = substr($trimmed, 4); + } else { + // simple in expression found + $e = $this->_tokenizer->sqlExplode($trimmed, ','); + + $value = array(); + foreach ($e as $part) { + $index = $table->enumIndex($field, trim($part, "'")); + + if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { + $index = $conn->quote($index, 'text'); + } + + if ($index !== false) { + $value[] = $index; + } else { + $value[] = $this->parseLiteralValue($part); + } + } + + // Change due to bug "(" XXX ")" + //$value = '(' . implode(', ', $value) . ')'; + $value = implode(', ', $value); + } + } else { + if ($enumIndex !== false) { + $value = $enumIndex; + } else { + $value = $this->parseLiteralValue($value); + } + } + + switch ($operator) { + case '<': + case '>': + case '=': + case '!=': + if ($enumIndex !== false) { + $value = $enumIndex; + } + default: + $leftExpr = (($hasLeftAggExpression) ? $leftMatches[1] . '(' : '') + . $alias . '.' . $field + . (($hasLeftAggExpression) ? $leftMatches[3] . ')' : '') ; + + $rightExpr = (($hasRightAggExpression) ? $rightMatches[1] . '(' : '') + . $value + . (($hasRightAggExpression) ? $rightMatches[3] . ')' : '') ; + + $condition = $leftExpr . ' ' . $operator . ' ' . $rightExpr; + } + + } + + return $condition; + } +} diff --git a/src/orm/query/limit.php b/src/orm/query/limit.php new file mode 100644 index 0000000..f0cd5ae --- /dev/null +++ b/src/orm/query/limit.php @@ -0,0 +1,9 @@ +query->parseClause($r); + + $ret[] = $r; + } + return $ret; + } +} diff --git a/src/orm/query/parser.php b/src/orm/query/parser.php new file mode 100644 index 0000000..71d6f1b --- /dev/null +++ b/src/orm/query/parser.php @@ -0,0 +1,5 @@ +query = $query; + if ( ! $tokenizer) { + $tokenizer = new IPF_ORM_Query_Tokenizer(); + } + $this->_tokenizer = $tokenizer; + } + + public function getQuery() + { + return $this->query; + } +} diff --git a/src/orm/query/registry.php b/src/orm/query/registry.php new file mode 100644 index 0000000..f35ff42 --- /dev/null +++ b/src/orm/query/registry.php @@ -0,0 +1,39 @@ +_queries[$key] = $query; + } else { + // namespace found + $e = explode('/', $key); + + $this->_queries[$e[0]][$e[1]] = $query; + } + } + + public function get($key, $namespace = null) + { + if (isset($namespace)) { + if ( ! isset($this->_queries[$namespace][$key])) { + throw new IPF_ORM_Exception('A query with the name ' . $namespace . '/' . $key . ' does not exist.'); + } + $query = $this->_queries[$namespace][$key]; + } else { + if ( ! isset($this->_queries[$key])) { + throw new IPF_ORM_Exception('A query with the name ' . $key . ' does not exist.'); + } + $query = $this->_queries[$key]; + } + + if ( ! ($query instanceof IPF_ORM_Query)) { + $query = IPF_ORM_Query::create()->parseQuery($query); + } + + return $query; + } +} \ No newline at end of file diff --git a/src/orm/query/select.php b/src/orm/query/select.php new file mode 100644 index 0000000..0994a68 --- /dev/null +++ b/src/orm/query/select.php @@ -0,0 +1,9 @@ +query->parseSelect($dql); + } +} \ No newline at end of file diff --git a/src/orm/query/set.php b/src/orm/query/set.php new file mode 100644 index 0000000..5e26e3b --- /dev/null +++ b/src/orm/query/set.php @@ -0,0 +1,27 @@ +_tokenizer->sqlExplode($dql, ' '); + foreach ($terms as $term) { + preg_match_all("/[a-z0-9_]+\.[a-z0-9_]+[\.[a-z0-9]+]*/i", $term, $m); + + if (isset($m[0])) { + foreach ($m[0] as $part) { + $e = explode('.', trim($part)); + $field = array_pop($e); + + $reference = implode('.', $e); + + $alias = $this->query->getTableAlias($reference); + $map = $this->query->getAliasDeclaration($reference); + + $dql = str_replace($part, $map['table']->getColumnName($field), $dql); + } + } + } + return $dql; + } +} \ No newline at end of file diff --git a/src/orm/query/tokenizer.php b/src/orm/query/tokenizer.php new file mode 100644 index 0000000..f9a7d13 --- /dev/null +++ b/src/orm/query/tokenizer.php @@ -0,0 +1,235 @@ +sqlExplode($query, ' '); + + foreach ($tokens as $index => $token) { + $token = trim($token); + switch (strtolower($token)) { + case 'delete': + case 'update': + case 'select': + case 'set': + case 'from': + case 'where': + case 'limit': + case 'offset': + case 'having': + $p = $token; + //$parts[$token] = array(); + $parts[$token] = ''; + break; + case 'order': + case 'group': + $i = ($index + 1); + if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') { + $p = $token; + $parts[$token] = ''; + //$parts[$token] = array(); + } else { + $parts[$p] .= "$token "; + //$parts[$p][] = $token; + } + break; + case 'by': + continue; + default: + if ( ! isset($p)) { + throw new IPF_ORM_Exception( + "Couldn't tokenize query. Encountered invalid token: '$token'."); + } + + $parts[$p] .= "$token "; + //$parts[$p][] = $token; + } + } + return $parts; + } + + public function bracketTrim($str, $e1 = '(', $e2 = ')') + { + if (substr($str, 0, 1) === $e1 && substr($str, -1) === $e2) { + return substr($str, 1, -1); + } else { + return $str; + } + } + + public function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')') + { + if (is_array($d)) { + $a = preg_split('#('.implode('|', $d).')#i', $str); + $d = stripslashes($d[0]); + } else { + $a = explode($d, $str); + } + + $i = 0; + $term = array(); + foreach($a as $key=>$val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + $s1 = substr_count($term[$i], $e1); + $s2 = substr_count($term[$i], $e2); + + if ($s1 == $s2) { + $i++; + } + } else { + $term[$i] .= $d . trim($val); + $c1 = substr_count($term[$i], $e1); + $c2 = substr_count($term[$i], $e2); + + if ($c1 == $c2) { + $i++; + } + } + } + return $term; + } + + public function quoteExplode($str, $d = ' ') + { + if (is_array($d)) { + $a = preg_split('/('.implode('|', $d).')/', $str); + $d = stripslashes($d[0]); + } else { + $a = explode($d, $str); + } + + $i = 0; + $term = array(); + foreach ($a as $key => $val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + + if ( ! (substr_count($term[$i], "'") & 1)) { + $i++; + } + } else { + $term[$i] .= $d . trim($val); + + if ( ! (substr_count($term[$i], "'") & 1)) { + $i++; + } + } + } + return $term; + } + + public function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')') + { + if ($d == ' ') { + $d = array(' ', '\s'); + } + if (is_array($d)) { + $d = array_map('preg_quote', $d); + + if (in_array(' ', $d)) { + $d[] = '\s'; + } + + $split = '#(' . implode('|', $d) . ')#'; + + $str = preg_split($split, $str); + $d = stripslashes($d[0]); + } else { + $str = explode($d, $str); + } + + $i = 0; + $term = array(); + + foreach ($str as $key => $val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + + $s1 = substr_count($term[$i], $e1); + $s2 = substr_count($term[$i], $e2); + + if (strpos($term[$i], '(') !== false) { + if ($s1 == $s2) { + $i++; + } + } else { + if ( ! (substr_count($term[$i], "'") & 1) && + ! (substr_count($term[$i], "\"") & 1)) { + $i++; + } + } + } else { + $term[$i] .= $d . trim($val); + $c1 = substr_count($term[$i], $e1); + $c2 = substr_count($term[$i], $e2); + + if (strpos($term[$i], '(') !== false) { + if ($c1 == $c2) { + $i++; + } + } else { + if ( ! (substr_count($term[$i], "'") & 1) && + ! (substr_count($term[$i], "\"") & 1)) { + $i++; + } + } + } + } + return $term; + } + + public function clauseExplode($str, array $d, $e1 = '(', $e2 = ')') + { + if (is_array($d)) { + $d = array_map('preg_quote', $d); + + if (in_array(' ', $d)) { + $d[] = '\s'; + } + + $split = '#(' . implode('|', $d) . ')#'; + + $str = preg_split($split, $str, -1, PREG_SPLIT_DELIM_CAPTURE); + } + + $i = 0; + $term = array(); + + foreach ($str as $key => $val) { + if ($key & 1) { + if (isset($term[($i - 1)]) && ! is_array($term[($i - 1)])) { + $term[($i - 1)] = array($term[($i - 1)], $val); + } + continue; + } + if (empty($term[$i])) { + $term[$i] = $val; + } else { + $term[$i] .= $str[($key - 1)] . $val; + } + + $c1 = substr_count($term[$i], $e1); + $c2 = substr_count($term[$i], $e2); + + if (strpos($term[$i], '(') !== false) { + if ($c1 == $c2) { + $i++; + } + } else { + if ( ! (substr_count($term[$i], "'") & 1) && + ! (substr_count($term[$i], "\"") & 1)) { + $i++; + } + } + } + + if (isset($term[$i - 1])) { + $term[$i - 1] = array($term[$i - 1], ''); + } + + return $term; + } +} diff --git a/src/orm/query/where.php b/src/orm/query/where.php new file mode 100644 index 0000000..1f51bbc --- /dev/null +++ b/src/orm/query/where.php @@ -0,0 +1,143 @@ +_tokenizer->bracketTrim(trim($where)); + $conn = $this->query->getConnection(); + $terms = $this->_tokenizer->sqlExplode($where); + + if (count($terms) > 1) { + if (substr($where, 0, 6) == 'EXISTS') { + return $this->parseExists($where, true); + } elseif (substr($where, 0, 10) == 'NOT EXISTS') { + return $this->parseExists($where, false); + } + } + + if (count($terms) < 3) { + $terms = $this->_tokenizer->sqlExplode($where, array('=', '<', '<>', '>', '!=')); + } + + if (count($terms) > 1) { + $first = array_shift($terms); + $value = array_pop($terms); + $operator = trim(substr($where, strlen($first), -strlen($value))); + $table = null; + $field = null; + + if (strpos($first, "'") === false && strpos($first, '(') === false) { + // normal field reference found + $a = explode('.', $first); + + $field = array_pop($a); + $reference = implode('.', $a); + + if (empty($reference)) { + $map = $this->query->getRootDeclaration(); + + $alias = $this->query->getTableAlias($this->query->getRootAlias()); + $table = $map['table']; + } else { + $map = $this->query->load($reference, false); + + $alias = $this->query->getTableAlias($reference); + $table = $map['table']; + } + } + $first = $this->query->parseClause($first); + + $sql = $first . ' ' . $operator . ' ' . $this->parseValue($value, $table, $field); + + return $sql; + } else { + return $where; + } + } + + public function parseValue($value, IPF_ORM_Table $table = null, $field = null) + { + $conn = $this->query->getConnection(); + + if (substr($value, 0, 1) == '(') { + // trim brackets + $trimmed = $this->_tokenizer->bracketTrim($value); + + if (substr($trimmed, 0, 4) == 'FROM' || + substr($trimmed, 0, 6) == 'SELECT') { + + // subquery found + $q = new IPF_ORM_Query(); + $value = '(' . $this->query->createSubquery()->parseQuery($trimmed, false)->getQuery() . ')'; + + } elseif (substr($trimmed, 0, 4) == 'SQL:') { + $value = '(' . substr($trimmed, 4) . ')'; + } else { + // simple in expression found + $e = $this->_tokenizer->sqlExplode($trimmed, ','); + + $value = array(); + + $index = false; + + foreach ($e as $part) { + if (isset($table) && isset($field)) { + $index = $table->enumIndex($field, trim($part, "'")); + + if (false !== $index && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { + $index = $conn->quote($index, 'text'); + } + } + + if ($index !== false) { + $value[] = $index; + } else { + $value[] = $this->parseLiteralValue($part); + } + } + + $value = '(' . implode(', ', $value) . ')'; + } + } else if (substr($value, 0, 1) == ':' || $value === '?') { + // placeholder found + if (isset($table) && isset($field) && $table->getTypeOf($field) == 'enum') { + $this->query->addEnumParam($value, $table, $field); + } else { + $this->query->addEnumParam($value, null, null); + } + } else { + $enumIndex = false; + if (isset($table) && isset($field)) { + // check if value is enumerated value + $enumIndex = $table->enumIndex($field, trim($value, "'")); + + if (false !== $enumIndex && $conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { + $enumIndex = $conn->quote($enumIndex, 'text'); + } + } + + if ($enumIndex !== false) { + $value = $enumIndex; + } else { + $value = $this->parseLiteralValue($value); + } + } + return $value; + } + + public function parseExists($where, $negation) + { + $operator = ($negation) ? 'EXISTS' : 'NOT EXISTS'; + + $pos = strpos($where, '('); + + if ($pos == false) { + throw new IPF_ORM_Exception('Unknown expression, expected a subquery with () -marks'); + } + + $sub = $this->_tokenizer->bracketTrim(substr($where, $pos)); + + return $operator . ' (' . $this->query->createSubquery()->parseQuery($sub, false)->getQuery() . ')'; + } +} diff --git a/src/orm/rawsql.php b/src/orm/rawsql.php new file mode 100644 index 0000000..fce5016 --- /dev/null +++ b/src/orm/rawsql.php @@ -0,0 +1,233 @@ +_parseSelectFields($queryPart, $append); + return $this; + } + + if (!isset($this->parts[$queryPartName])) { + $this->_sqlParts[$queryPartName] = array(); + } + + if (!$append) { + $this->_sqlParts[$queryPartName] = array($queryPart); + } else { + $this->_sqlParts[$queryPartName][] = $queryPart; + } + return $this; + } + + protected function _addDqlQueryPart($queryPartName, $queryPart, $append=false) + { + return $this->parseDqlQueryPart($queryPartName, $queryPart, $append); + } + + private function _parseSelectFields($queryPart, $append=false) + { + if ($append) + $this->fields[] = $queryPart; + else + $this->fields = array($queryPart); + + $this->_sqlParts['select'] = array(); + } + + public function parseDqlQuery($query) + { + $this->_parseSelectFields($query); + $this->clear(); + + $tokens = $this->_tokenizer->sqlExplode($query, ' '); + + $parts = array(); + foreach ($tokens as $key => $part) { + $partLowerCase = strtolower($part); + switch ($partLowerCase) { + case 'select': + case 'from': + case 'where': + case 'limit': + case 'offset': + case 'having': + $type = $partLowerCase; + if ( ! isset($parts[$partLowerCase])) { + $parts[$partLowerCase] = array(); + } + break; + case 'order': + case 'group': + $i = $key + 1; + if (isset($tokens[$i]) && strtolower($tokens[$i]) === 'by') { + $type = $partLowerCase . 'by'; + $parts[$type] = array(); + } else { + //not a keyword so we add it to the previous type + $parts[$type][] = $part; + } + break; + case 'by': + continue; + default: + //not a keyword so we add it to the previous type. + if ( ! isset($parts[$type][0])) { + $parts[$type][0] = $part; + } else { + // why does this add to index 0 and not append to the + // array. If it had done that one could have used + // parseQueryPart. + $parts[$type][0] .= ' '.$part; + } + } + } + + $this->_sqlParts = $parts; + $this->_sqlParts['select'] = array(); + + return $this; + } + + public function getSqlQuery($params=array()) + { + $select = array(); + + foreach ($this->fields as $field) { + if (preg_match('/^{([^}{]+)\.([^}{]+)}$/U', $field, $e)) { + // try to auto-add component + if (!$this->hasSqlTableAlias($e[1])) { + try { + $this->addComponent($e[1], ucwords($e[1])); + } catch (IPF_ORM_Exception $exception) { + throw new IPF_ORM_Exception('The associated component for table alias ' . $e[1] . ' couldn\'t be found.'); + } + } + + $componentAlias = $this->getComponentAlias($e[1]); + + if ($e[2] == '*') { + foreach ($this->_queryComponents[$componentAlias]['table']->getColumnNames() as $name) { + $field = $e[1] . '.' . $name; + + $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $name; + } + } else { + $field = $e[1] . '.' . $e[2]; + $select[$componentAlias][$field] = $field . ' AS ' . $e[1] . '__' . $e[2]; + } + } else { + $select['__raw__'][] = $field; + } + } + + // force-add all primary key fields + + foreach ($this->getTableAliasMap() as $tableAlias => $componentAlias) { + $map = $this->_queryComponents[$componentAlias]; + + foreach ((array) $map['table']->getIdentifierColumnNames() as $key) { + $field = $tableAlias . '.' . $key; + + if ( ! isset($this->_sqlParts['select'][$field])) { + $select[$componentAlias][$field] = $field . ' AS ' . $tableAlias . '__' . $key; + } + } + } + + // first add the fields of the root component + reset($this->_queryComponents); + $componentAlias = key($this->_queryComponents); + + $q = 'SELECT ' . implode(', ', $select[$componentAlias]); + unset($select[$componentAlias]); + + foreach ($select as $component => $fields) { + if ( ! empty($fields)) { + $q .= ', ' . implode(', ', $fields); + } + } + + $string = $this->getInheritanceCondition($this->getRootAlias()); + if ( ! empty($string)) { + $this->_sqlParts['where'][] = $string; + } + $copy = $this->_sqlParts; + unset($copy['select']); + + $q .= ( ! empty($this->_sqlParts['from']))? ' FROM ' . implode(' ', $this->_sqlParts['from']) : ''; + $q .= ( ! empty($this->_sqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_sqlParts['where']) : ''; + $q .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby']) : ''; + $q .= ( ! empty($this->_sqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_sqlParts['having']) : ''; + $q .= ( ! empty($this->_sqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby']) : ''; + $q .= ( ! empty($this->_sqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_sqlParts['limit']) : ''; + $q .= ( ! empty($this->_sqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_sqlParts['offset']) : ''; + + if ( ! empty($string)) { + array_pop($this->_sqlParts['where']); + } + return $q; + } + + public function getFields() + { + return $this->fields; + } + + public function addComponent($tableAlias, $path) + { + $tmp = explode(' ', $path); + $originalAlias = (count($tmp) > 1) ? end($tmp) : null; + + $e = explode('.', $tmp[0]); + + $fullPath = $tmp[0]; + $fullLength = strlen($fullPath); + + $table = null; + + $currPath = ''; + + if (isset($this->_queryComponents[$e[0]])) { + $table = $this->_queryComponents[$e[0]]['table']; + $currPath = $parent = array_shift($e); + } + + foreach ($e as $k => $component) { + // get length of the previous path + $length = strlen($currPath); + + // build the current component path + $currPath = ($currPath) ? $currPath . '.' . $component : $component; + + $delimeter = substr($fullPath, $length, 1); + + // if an alias is not given use the current path as an alias identifier + if (strlen($currPath) === $fullLength && isset($originalAlias)) { + $componentAlias = $originalAlias; + } else { + $componentAlias = $currPath; + } + + if (!isset($table)) { + $table = IPF_ORM_Manager::connection()->getTable($component); + $this->_queryComponents[$componentAlias] = array('table' => $table); + } else { + $relation = $table->getRelation($component); + + $this->_queryComponents[$componentAlias] = array('table' => $relation->getTable(), + 'parent' => $parent, + 'relation' => $relation); + } + $this->addSqlTableAlias($tableAlias, $componentAlias); + + $parent = $currPath; + } + + return $this; + } +} + diff --git a/src/orm/record.php b/src/orm/record.php new file mode 100644 index 0000000..8be51ab --- /dev/null +++ b/src/orm/record.php @@ -0,0 +1,1212 @@ +_table = $table; + $exists = ( ! $isNewEntry); + } else { + // get the table of this class + $class = get_class($this); + $this->_table = IPF_ORM::getTable($class); + $exists = false; + } + + // Check if the current connection has the records table in its registry + // If not this record is only used for creating table definition and setting up + // relations. + if ( ! $this->_table->getConnection()->hasTable($this->_table->getComponentName())) { + return; + } + + $this->_oid = self::$_index; + + self::$_index++; + + // get the data array + $this->_data = $this->_table->getData(); + + // get the column count + $count = count($this->_data); + + $this->_values = $this->cleanData($this->_data); + + $this->prepareIdentifiers($exists); + + if ( ! $exists) { + if ($count > count($this->_values)) { + $this->_state = IPF_ORM_Record::STATE_TDIRTY; + } else { + $this->_state = IPF_ORM_Record::STATE_TCLEAN; + } + + // set the default values for this record + $this->assignDefaultValues(); + } else { + $this->_state = IPF_ORM_Record::STATE_CLEAN; + + if ($count < $this->_table->getColumnCount()) { + $this->_state = IPF_ORM_Record::STATE_PROXY; + } + } + + $repository = $this->_table->getRepository(); + $repository->add($this); + + $this->construct(); + } + + public static function _index() + { + return self::$_index; + } + + public function construct(){} + + public function getOid() + { + return $this->_oid; + } + + public function oid() + { + return $this->_oid; + } + + public function isValid() + { + // Clear the stack from any previous errors. + $this->_errors = array(); + + // Run validation process + $validator = new IPF_ORM_Validator(); + $validator->validateRecord($this); + $this->validate(); + if ($this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) { + $this->validateOnInsert(); + } else { + $this->validateOnUpdate(); + } + + return count($this->_errors) === 0; + } + + public function addError($invalidFieldName, $errorCode = 'general') + { + $this->_errors[$invalidFieldName][] = $errorCode; + } + + public function getErrors() + { + return $this->_errors; + } + + protected function validate(){} + + protected function validateOnUpdate(){} + + protected function validateOnInsert(){} + + public function preSerialize($event){} + + public function postSerialize($event){} + + public function preUnserialize($event){} + + public function postUnserialize($event){} + + public function preSave($event){} + + public function postSave($event){} + + public function preDelete($event){} + + public function postDelete($event){} + + public function preUpdate($event){} + + public function postUpdate($event){} + + public function preInsert($event){} + + public function postInsert($event){} + + public function assignDefaultValues($overwrite = false) + { + if ( ! $this->_table->hasDefaultValues()) { + return false; + } + foreach ($this->_data as $column => $value) { + $default = $this->_table->getDefaultValueOf($column); + + if ($default === null) { + continue; + } + + if (IPF_ORM_Null::isNull($value) || $overwrite) { + $this->_data[$column] = $default; + $this->_modified[] = $column; + $this->_state = IPF_ORM_Record::STATE_TDIRTY; + } + } + } + + public function cleanData(&$data) + { + $tmp = $data; + $data = array(); + + foreach ($this->getTable()->getFieldNames() as $fieldName) { + if (isset($tmp[$fieldName])) { + $data[$fieldName] = $tmp[$fieldName]; + } else if (array_key_exists($fieldName, $tmp)) { + $data[$fieldName] = IPF_ORM_Null::getInstance(); + } else if (!isset($this->_data[$fieldName])) { + $data[$fieldName] = IPF_ORM_Null::getInstance(); + } + unset($tmp[$fieldName]); + } + + return $tmp; + } + + public function hydrate(array $data) + { + $this->_values = array_merge($this->_values, $this->cleanData($data)); + $this->_data = array_merge($this->_data, $data); + $this->prepareIdentifiers(true); + } + + private function prepareIdentifiers($exists = true) + { + switch ($this->_table->getIdentifierType()) { + case IPF_ORM::IDENTIFIER_AUTOINC: + case IPF_ORM::IDENTIFIER_NATURAL: + $name = $this->_table->getIdentifier(); + if (is_array($name)) { + $name = $name[0]; + } + if ($exists) { + if (isset($this->_data[$name]) && !IPF_ORM_Null::isNull($this->_data[$name])) { + $this->_id[$name] = $this->_data[$name]; + } + } + break; + case IPF_ORM::IDENTIFIER_COMPOSITE: + $names = $this->_table->getIdentifier(); + + foreach ($names as $name) { + if (IPF_ORM_Null::isNull($this->_data[$name])) { + $this->_id[$name] = null; + } else { + $this->_id[$name] = $this->_data[$name]; + } + } + break; + } + } + + public function serialize() + { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_SERIALIZE); + + $this->preSerialize($event); + + $vars = get_object_vars($this); + + unset($vars['_references']); + unset($vars['_table']); + unset($vars['_errors']); + unset($vars['_filter']); + + $name = $this->_table->getIdentifier(); + $this->_data = array_merge($this->_data, $this->_id); + + foreach ($this->_data as $k => $v) { + if ($v instanceof IPF_ORM_Record && $this->_table->getTypeOf($k) != 'object') { + unset($vars['_data'][$k]); + } elseif (IPF_ORM_Null::isNull($v)) { + unset($vars['_data'][$k]); + } else { + switch ($this->_table->getTypeOf($k)) { + case 'array': + case 'object': + $vars['_data'][$k] = serialize($vars['_data'][$k]); + break; + case 'gzip': + $vars['_data'][$k] = gzcompress($vars['_data'][$k]); + break; + case 'enum': + $vars['_data'][$k] = $this->_table->enumIndex($k, $vars['_data'][$k]); + break; + } + } + } + + $str = serialize($vars); + + $this->postSerialize($event); + + return $str; + } + + public function unserialize($serialized) + { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::RECORD_UNSERIALIZE); + + $this->preUnserialize($event); + + $connection = IPF_ORM_Manager::connection(); + + $this->_oid = self::$_index; + self::$_index++; + + $this->_table = $connection->getTable(get_class($this)); + + $array = unserialize($serialized); + + foreach($array as $k => $v) { + $this->$k = $v; + } + + foreach ($this->_data as $k => $v) { + switch ($this->_table->getTypeOf($k)) { + case 'array': + case 'object': + $this->_data[$k] = unserialize($this->_data[$k]); + break; + case 'gzip': + $this->_data[$k] = gzuncompress($this->_data[$k]); + break; + case 'enum': + $this->_data[$k] = $this->_table->enumValue($k, $this->_data[$k]); + break; + + } + } + + $this->_table->getRepository()->add($this); + + $this->cleanData($this->_data); + + $this->prepareIdentifiers($this->exists()); + + $this->postUnserialize($event); + } + + public function state($state = null) + { + if ($state == null) { + return $this->_state; + } + + $err = false; + if (is_integer($state)) { + if ($state >= 1 && $state <= 6) { + $this->_state = $state; + } else { + $err = true; + } + } else if (is_string($state)) { + $upper = strtoupper($state); + + $const = 'IPF_ORM_Record::STATE_' . $upper; + if (defined($const)) { + $this->_state = constant($const); + } else { + $err = true; + } + } + + if ($this->_state === IPF_ORM_Record::STATE_TCLEAN || + $this->_state === IPF_ORM_Record::STATE_CLEAN) { + $this->_modified = array(); + } + + if ($err) { + throw new IPF_ORM_Exception('Unknown record state ' . $state); + } + } + + public function refresh($deep = false) + { + $id = $this->identifier(); + if ( ! is_array($id)) { + $id = array($id); + } + if (empty($id)) { + return false; + } + $id = array_values($id); + + if ($deep) { + $query = $this->getTable()->createQuery(); + foreach (array_keys($this->_references) as $name) { + $query->leftJoin(get_class($this) . '.' . $name); + } + $query->where(implode(' = ? AND ', $this->getTable()->getIdentifierColumnNames()) . ' = ?'); + $this->clearRelated(); + $record = $query->fetchOne($id); + } else { + // Use FETCH_ARRAY to avoid clearing object relations + $record = $this->getTable()->find($id, IPF_ORM::HYDRATE_ARRAY); + if ($record) { + $this->hydrate($record); + } + } + + if ($record === false) { + throw new IPF_ORM_Exception('Failed to refresh. Record does not exist.'); + } + + $this->_modified = array(); + + $this->prepareIdentifiers(); + + $this->_state = IPF_ORM_Record::STATE_CLEAN; + + return $this; + } + + public function refreshRelated($name = null) + { + if (is_null($name)) { + foreach ($this->_table->getRelations() as $rel) { + $this->_references[$rel->getAlias()] = $rel->fetchRelatedFor($this); + } + } else { + $rel = $this->_table->getRelation($name); + $this->_references[$name] = $rel->fetchRelatedFor($this); + } + } + + public function clearRelated() + { + $this->_references = array(); + } + + public function getTable() + { + return $this->_table; + } + + public function getData() + { + return $this->_data; + } + + public function rawGet($fieldName) + { + if ( ! isset($this->_data[$fieldName])) { + throw new IPF_ORM_Exception('Unknown property '. $fieldName); + } + if (IPF_ORM_Null::isNull($this->_data[$fieldName])) { + return null; + } + + return $this->_data[$fieldName]; + } + + public function load() + { + // only load the data from database if the IPF_ORM_Record is in proxy state + if ($this->_state == IPF_ORM_Record::STATE_PROXY) { + $this->refresh(); + $this->_state = IPF_ORM_Record::STATE_CLEAN; + return true; + } + return false; + } + + public function get($fieldName, $load = true) + { + $value = IPF_ORM_Null::getInstance(); + + if (isset($this->_data[$fieldName])) { + if (IPF_ORM_Null::isNull($this->_data[$fieldName]) && $load) { + $this->load(); + } + if (IPF_ORM_Null::isNull($this->_data[$fieldName])) { + $value = null; + } else { + $value = $this->_data[$fieldName]; + } + return $value; + } + + if (isset($this->_values[$fieldName])) { + return $this->_values[$fieldName]; + } + + if (!isset($this->_references[$fieldName]) && $load) { + $rel = $this->_table->getRelation($fieldName); + $this->_references[$fieldName] = $rel->fetchRelatedFor($this); + } + return $this->_references[$fieldName]; + } + + public function mapValue($name, $value) + { + $this->_values[$name] = $value; + } + + public function set($fieldName, $value, $load = true) + { + if (isset($this->_data[$fieldName])) { + $type = $this->_table->getTypeOf($fieldName); + if ($value instanceof IPF_ORM_Record) { + $id = $value->getIncremented(); + + if ($id !== null && $type !== 'object') { + $value = $id; + } + } + + if ($load) { + $old = $this->get($fieldName, $load); + } else { + $old = $this->_data[$fieldName]; + } + + if ($this->_isValueModified($type, $old, $value)) { + if ($value === null) { + $value = IPF_ORM_Null::getInstance(); + } + + $this->_data[$fieldName] = $value; + $this->_modified[] = $fieldName; + switch ($this->_state) { + case IPF_ORM_Record::STATE_CLEAN: + $this->_state = IPF_ORM_Record::STATE_DIRTY; + break; + case IPF_ORM_Record::STATE_TCLEAN: + $this->_state = IPF_ORM_Record::STATE_TDIRTY; + break; + } + } + } else { + $this->coreSetRelated($fieldName, $value); + } + + return $this; + } + + protected function _isValueModified($type, $old, $new) + { + if ($type == 'boolean' && (is_bool($old) || is_numeric($old)) && (is_bool($new) || is_numeric($new)) && $old == $new) { + return false; + } else { + return $old !== $new; + } + } + + public function coreSetRelated($name, $value) + { + $rel = $this->_table->getRelation($name); + + if ($value === null) { + $value = IPF_ORM_Null::getInstance(); + } + + // one-to-many or one-to-one relation + if ($rel instanceof IPF_ORM_Relation_ForeignKey || $rel instanceof IPF_ORM_Relation_LocalKey) { + if ( ! $rel->isOneToOne()) { + // one-to-many relation found + if ( ! ($value instanceof IPF_ORM_Collection)) { + throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting one-to-many references."); + } + if (isset($this->_references[$name])) { + $this->_references[$name]->setData($value->getData()); + return $this; + } + } else { + if (!IPF_ORM_Null::isNull($value)) { + $relatedTable = $value->getTable(); + $foreignFieldName = $relatedTable->getFieldName($rel->getForeign()); + $localFieldName = $this->_table->getFieldName($rel->getLocal()); + + // one-to-one relation found + if ( ! ($value instanceof IPF_ORM_Record)) { + throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Record or IPF_ORM_Null when setting one-to-one references."); + } + if ($rel instanceof IPF_ORM_Relation_LocalKey) { + if ( ! empty($foreignFieldName) && $foreignFieldName != $value->getTable()->getIdentifier()) { + $this->set($localFieldName, $value->rawGet($foreignFieldName), false); + } else { + $this->set($localFieldName, $value, false); + } + } else { + $value->set($foreignFieldName, $this, false); + } + } + } + + } else if ($rel instanceof IPF_ORM_Relation_Association) { + // join table relation found + if ( ! ($value instanceof IPF_ORM_Collection)) { + throw new IPF_ORM_Exception("Couldn't call IPF_ORM::set(), second argument should be an instance of IPF_ORM_Collection when setting many-to-many references."); + } + } + + $this->_references[$name] = $value; + } + + public function contains($fieldName) + { + if (isset($this->_data[$fieldName])) { + // this also returns true if the field is a IPF_ORM_Null. + // imho this is not correct behavior. + return true; + } + if (isset($this->_id[$fieldName])) { + return true; + } + if (isset($this->_values[$fieldName])) { + return true; + } + if (isset($this->_references[$fieldName]) && !IPF_ORM_Null::isNull($this->_references[$fieldName])) { + return true; + } + return false; + } + + public function __unset($name) + { + if (isset($this->_data[$name])) { + $this->_data[$name] = array(); + } else if (isset($this->_references[$name])) { + if ($this->_references[$name] instanceof IPF_ORM_Record) { + $this->_pendingDeletes[] = $this->$name; + $this->_references[$name] = IPF_ORM_Null::getInstance(); + } elseif ($this->_references[$name] instanceof IPF_ORM_Collection) { + $this->_pendingDeletes[] = $this->$name; + $this->_references[$name]->setData(array()); + } + } + } + + public function getPendingDeletes() + { + return $this->_pendingDeletes; + } + + public function save(IPF_ORM_Connection $conn = null) + { + if ($conn === null) { + $conn = $this->_table->getConnection(); + } + $conn->unitOfWork->saveGraph($this); + } + + public function trySave(IPF_ORM_Connection $conn = null) { + try { + $this->save($conn); + return true; + } catch (IPF_ORM_Exception_Validator $ignored) { + return false; + } + } + + public function replace(IPF_ORM_Connection $conn = null) + { + if ($conn === null) { + $conn = $this->_table->getConnection(); + } + + if ($this->exists()) { + return $this->save(); + } else { + $identifier = (array) $this->getTable()->getIdentifier(); + return $conn->replace($this->_table, $this->toArray(), $identifier); + } + } + + public function getModified() + { + $a = array(); + + foreach ($this->_modified as $k => $v) { + $a[$v] = $this->_data[$v]; + } + return $a; + } + + public function modifiedFields() + { + $a = array(); + + foreach ($this->_modified as $k => $v) { + $a[$v] = $this->_data[$v]; + } + return $a; + } + + public function getPrepared(array $array = array()) + { + $a = array(); + + if (empty($array)) { + $modifiedFields = $this->_modified; + } + + foreach ($modifiedFields as $field) { + $type = $this->_table->getTypeOf($field); + + if (IPF_ORM_Null::isNull($this->_data[$field])) { + $a[$field] = null; + continue; + } + + switch ($type) { + case 'array': + case 'object': + $a[$field] = serialize($this->_data[$field]); + break; + case 'gzip': + $a[$field] = gzcompress($this->_data[$field],5); + break; + case 'boolean': + $a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]); + break; + case 'enum': + $a[$field] = $this->_table->enumIndex($field, $this->_data[$field]); + break; + default: + if ($this->_data[$field] instanceof IPF_ORM_Record) { + $a[$field] = $this->_data[$field]->getIncremented(); + if ($a[$field] !== null) { + $this->_data[$field] = $a[$field]; + } + } else { + $a[$field] = $this->_data[$field]; + } + /** TODO: + if ($this->_data[$v] === null) { + throw new IPF_ORM_Record_Exception('Unexpected null value.'); + } + */ + } + } + $map = $this->_table->inheritanceMap; + foreach ($map as $k => $v) { + $k = $this->_table->getFieldName($k); + $old = $this->get($k, false); + + if ((string) $old !== (string) $v || $old === null) { + $a[$k] = $v; + $this->_data[$k] = $v; + } + } + + return $a; + } + + public function count() + { + return count($this->_data); + } + + public function columnCount() + { + return $this->count(); + } + + public function toArray($deep = true, $prefixKey = false) + { + if ($this->_state == self::STATE_LOCKED) { + return false; + } + + $stateBeforeLock = $this->_state; + $this->_state = self::STATE_LOCKED; + + $a = array(); + + foreach ($this as $column => $value) { + if (IPF_ORM_Null::isNull($value) || is_object($value)) { + $value = null; + } + + $a[$column] = $value; + } + + if ($this->_table->getIdentifierType() == IPF_ORM::IDENTIFIER_AUTOINC) { + $i = $this->_table->getIdentifier(); + $a[$i] = $this->getIncremented(); + } + + if ($deep) { + foreach ($this->_references as $key => $relation) { + if (!IPF_ORM_Null::isNull($relation)) { + $a[$key] = $relation->toArray($deep, $prefixKey); + } + } + } + + // [FIX] Prevent mapped IPF_ORM_Records from being displayed fully + foreach ($this->_values as $key => $value) { + if ($value instanceof IPF_ORM_Record) { + $a[$key] = $value->toArray($deep, $prefixKey); + } else { + $a[$key] = $value; + } + } + + $this->_state = $stateBeforeLock; + + return $a; + } + + public function merge($data, $deep = true) + { + if ($data instanceof $this) { + $array = $data->toArray($deep); + } else if (is_array($data)) { + $array = $data; + } + + return $this->fromArray($array, $deep); + } + + public function fromArray(array $array, $deep = true) + { + $refresh = false; + foreach ($array as $key => $value) { + if ($key == '_identifier') { + $refresh = true; + $this->assignIdentifier((array) $value); + continue; + } + + if ($deep && $this->getTable()->hasRelation($key)) { + $this->$key->fromArray($value, $deep); + } else if ($this->getTable()->hasField($key)) { + $this->set($key, $value); + } + } + + if ($refresh) { + $this->refresh(); + } + } + + protected function _synchronizeWithArrayForRelation($key, $value) + { + $this->get($key)->synchronizeWithArray($value); + } + + public function synchronizeWithArray(array $array, $deep = true) + { + $refresh = false; + foreach ($array as $key => $value) { + if ($key == '_identifier') { + $refresh = true; + $this->assignIdentifier((array) $value); + continue; + } + if ($deep && $this->getTable()->hasRelation($key)) { + $this->_synchronizeWithArrayForRelation($key, $value); + } else if ($this->getTable()->hasField($key)) { + $this->set($key, $value); + } + } + // eliminate relationships missing in the $array + foreach ($this->_references as $name => $obj) { + if ( ! isset($array[$name])) { + unset($this->$name); + } + } + if ($refresh) { + $this->refresh(); + } + } + + public function exists() + { + return ($this->_state !== IPF_ORM_Record::STATE_TCLEAN && + $this->_state !== IPF_ORM_Record::STATE_TDIRTY); + } + + public function isModified() + { + return ($this->_state === IPF_ORM_Record::STATE_DIRTY || + $this->_state === IPF_ORM_Record::STATE_TDIRTY); + } + + public function hasRelation($fieldName) + { + if (isset($this->_data[$fieldName]) || isset($this->_id[$fieldName])) { + return true; + } + return $this->_table->hasRelation($fieldName); + } + + public function getIterator() + { + return new IPF_ORM_Record_Iterator($this); + } + + public function delete(IPF_ORM_Connection $conn = null) + { + if ($conn == null) { + $conn = $this->_table->getConnection(); + } + return $conn->unitOfWork->delete($this); + } + + public function copy($deep = false) + { + $data = $this->_data; + + if ($this->_table->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC) { + $id = $this->_table->getIdentifier(); + + unset($data[$id]); + } + + $ret = $this->_table->create($data); + $modified = array(); + + foreach ($data as $key => $val) { + if (!IPF_ORM_Null::isNull($val)) { + $ret->_modified[] = $key; + } + } + + if ($deep) { + foreach ($this->_references as $key => $value) { + if ($value instanceof IPF_ORM_Collection) { + foreach ($value as $record) { + $ret->{$key}[] = $record->copy($deep); + } + } else if($value instanceof IPF_ORM_Record) { + $ret->set($key, $value->copy($deep)); + } + } + } + + return $ret; + } + + public function assignIdentifier($id = false) + { + if ($id === false) { + $this->_id = array(); + $this->_data = $this->cleanData($this->_data); + $this->_state = IPF_ORM_Record::STATE_TCLEAN; + $this->_modified = array(); + } elseif ($id === true) { + $this->prepareIdentifiers(true); + $this->_state = IPF_ORM_Record::STATE_CLEAN; + $this->_modified = array(); + } else { + if (is_array($id)) { + foreach ($id as $fieldName => $value) { + $this->_id[$fieldName] = $value; + $this->_data[$fieldName] = $value; + } + } else { + $name = $this->_table->getIdentifier(); + $this->_id[$name] = $id; + $this->_data[$name] = $id; + } + $this->_state = IPF_ORM_Record::STATE_CLEAN; + $this->_modified = array(); + } + } + + public function identifier() + { + return $this->_id; + } + + final public function getIncremented() + { + $id = current($this->_id); + if ($id === false) { + return null; + } + + return $id; + } + + public function pk($sep='_') + { + $pk = ''; + foreach($this->_id as $val) { + if ($pk!='') + $pk .= $sep; + $pk .= $val; + } + return $pk; + } + + public function getLast() + { + return $this; + } + + public function hasReference($name) + { + return isset($this->_references[$name]); + } + + public function reference($name) + { + if (isset($this->_references[$name])) { + return $this->_references[$name]; + } + } + + public function obtainReference($name) + { + if (isset($this->_references[$name])) { + return $this->_references[$name]; + } + throw new IPF_ORM_Exception("Unknown reference $name"); + } + + public function getReferences() + { + return $this->_references; + } + + final public function setRelated($alias, IPF_ORM_Access $coll) + { + $this->_references[$alias] = $coll; + } + + public function loadReference($name) + { + $rel = $this->_table->getRelation($name); + $this->_references[$name] = $rel->fetchRelatedFor($this); + } + + public function call($callback, $column) + { + $args = func_get_args(); + array_shift($args); + + if (isset($args[0])) { + $fieldName = $args[0]; + $args[0] = $this->get($fieldName); + + $newvalue = call_user_func_array($callback, $args); + + $this->_data[$fieldName] = $newvalue; + } + return $this; + } + + public function unlink($alias, $ids = array()) + { + $ids = (array) $ids; + + $q = new IPF_ORM_Query(); + + $rel = $this->getTable()->getRelation($alias); + + if ($rel instanceof IPF_ORM_Relation_Association) { + $q->delete() + ->from($rel->getAssociationTable()->getComponentName()) + ->where($rel->getLocal() . ' = ?', array_values($this->identifier())); + + if (count($ids) > 0) { + $q->whereIn($rel->getForeign(), $ids); + } + + $q->execute(); + + } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) { + $q->update($rel->getTable()->getComponentName()) + ->set($rel->getForeign(), '?', array(null)) + ->addWhere($rel->getForeign() . ' = ?', array_values($this->identifier())); + + if (count($ids) > 0) { + $q->whereIn($rel->getTable()->getIdentifier(), $ids); + } + + $q->execute(); + } + if (isset($this->_references[$alias])) { + foreach ($this->_references[$alias] as $k => $record) { + if (in_array(current($record->identifier()), $ids)) { + $this->_references[$alias]->remove($k); + } + } + $this->_references[$alias]->takeSnapshot(); + } + return $this; + } + + public function link($alias, $ids) + { + $ids = (array) $ids; + + if ( ! count($ids)) { + return $this; + } + + $identifier = array_values($this->identifier()); + $identifier = array_shift($identifier); + + $rel = $this->getTable()->getRelation($alias); + + if ($rel instanceof IPF_ORM_Relation_Association) { + + $modelClassName = $rel->getAssociationTable()->getComponentName(); + $localFieldName = $rel->getLocalFieldName(); + $localFieldDef = $rel->getAssociationTable()->getColumnDefinition($localFieldName); + if ($localFieldDef['type'] == 'integer') { + $identifier = (integer) $identifier; + } + $foreignFieldName = $rel->getForeignFieldName(); + $foreignFieldDef = $rel->getAssociationTable()->getColumnDefinition($foreignFieldName); + if ($foreignFieldDef['type'] == 'integer') { + for ($i = 0; $i < count($ids); $i++) { + $ids[$i] = (integer) $ids[$i]; + } + } + + foreach ($ids as $id) { + $record = new $modelClassName; + $record[$localFieldName] = $identifier; + $record[$foreignFieldName] = $id; + $record->save(); + } + + } else if ($rel instanceof IPF_ORM_Relation_ForeignKey) { + + $q = new IPF_ORM_Query(); + + $q->update($rel->getTable()->getComponentName()) + ->set($rel->getForeign(), '?', array_values($this->identifier())); + + if (count($ids) > 0) { + $q->whereIn($rel->getTable()->getIdentifier(), $ids); + } + + $q->execute(); + + } else if ($rel instanceof IPF_ORM_Relation_LocalKey) { + + $q = new IPF_ORM_Query(); + + $q->update($this->getTable()->getComponentName()) + ->set($rel->getLocalFieldName(), '?', $ids); + + if (count($ids) > 0) { + $q->whereIn($rel->getTable()->getIdentifier(), array_values($this->identifier())); + } + + $q->execute(); + + } + + return $this; + } + + public function __call($method, $args) + { + if (($template = $this->_table->getMethodOwner($method)) !== false) { + $template->setInvoker($this); + return call_user_func_array(array($template, $method), $args); + } + + foreach ($this->_table->getTemplates() as $template) { + if (method_exists($template, $method)) { + $template->setInvoker($this); + $this->_table->setMethodOwner($method, $template); + + return call_user_func_array(array($template, $method), $args); + } + } + + throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method)); + } + + public function free($deep = false) + { + if ($this->_state != self::STATE_LOCKED) { + $this->_state = self::STATE_LOCKED; + + $this->_table->getRepository()->evict($this->_oid); + $this->_table->removeRecord($this); + $this->_data = array(); + $this->_id = array(); + + if ($deep) { + foreach ($this->_references as $name => $reference) { + if (!IPF_ORM_Null::isNull($reference)) { + $reference->free($deep); + } + } + } + + $this->_references = array(); + } + } + + public function toString() + { + return IPF_ORM::dump(get_object_vars($this)); + } + + public function __toString() + { + return sprintf('<%s #%d>', get_class($this), $this->_oid); + } + + public function SetFromFormData($cleaned_values) + { + $names = $this->_table->getFieldNames(); + foreach ($cleaned_values as $key=>$val) { + $validators = $this->getTable()->getFieldValidators($key); + if ( + array_key_exists('image',$validators) || + array_key_exists('file',$validators) || + array_key_exists('email',$validators) + ){ + if (($val!==null) && ($val=='')) + continue; + } + if (array_search($key,$names)){ + $this->$key = $val; + } + } + } + + public function SetCustom($name, $val) + { + $this->_custom[$name] = $val; + } + + public function GetCustom($name) + { + if (isset($this->_custom[$name])) + return $this->_custom[$name]; + return null; + } +} + diff --git a/src/orm/record/abstract.php b/src/orm/record/abstract.php new file mode 100644 index 0000000..07aab0d --- /dev/null +++ b/src/orm/record/abstract.php @@ -0,0 +1,60 @@ +_table; + } + + public function setInheritanceMap($map) + { + $this->_table->setOption('inheritanceMap', $map); + } + + public function attribute($attr, $value) + { + if ($value == null) { + if (is_array($attr)) { + foreach ($attr as $k => $v) { + $this->_table->setAttribute($k, $v); + } + } else { + return $this->_table->getAttribute($attr); + } + } else { + $this->_table->setAttribute($attr, $value); + } + } + + public function check($constraint, $name = null) + { + if (is_array($constraint)) { + foreach ($constraint as $name => $def) { + $this->_table->addCheckConstraint($def, $name); + } + } else { + $this->_table->addCheckConstraint($constraint, $name); + } + return $this; + } + + public function __debugInfo() + { + $r = array(); + foreach ($this->getTable()->getColumnNames() as $column) + $r[$column] = $this->get($column); + return $r; + } +} + diff --git a/src/orm/record/iterator.php b/src/orm/record/iterator.php new file mode 100644 index 0000000..505a990 --- /dev/null +++ b/src/orm/record/iterator.php @@ -0,0 +1,24 @@ +record = $record; + parent::__construct($record->getData()); + } + + public function current() + { + $value = parent::current(); + + if (IPF_ORM_Null::isNull($value)) { + return null; + } else { + return $value; + } + } +} + diff --git a/src/orm/relation.php b/src/orm/relation.php new file mode 100644 index 0000000..3d0c550 --- /dev/null +++ b/src/orm/relation.php @@ -0,0 +1,175 @@ + true, + 'foreign' => true, + 'local' => true, + 'class' => true, + 'type' => true, + 'table' => true, + 'localTable' => true, + 'name' => null, + 'refTable' => null, + 'onDelete' => null, + 'onUpdate' => null, + 'deferred' => null, + 'deferrable' => null, + 'constraint' => null, + 'equal' => false, + 'cascade' => array(), // application-level cascades + 'owningSide' => false, // whether this is the owning side + 'exclude' => false, + ); + + public function __construct(array $definition) + { + $def = array(); + foreach ($this->definition as $key => $val) { + if ( ! isset($definition[$key]) && $val) { + throw new IPF_ORM_Exception($key . ' is required!'); + } + if (isset($definition[$key])) { + $def[$key] = $definition[$key]; + } else { + $def[$key] = $this->definition[$key]; + } + } + $this->definition = $def; + } + + public function hasConstraint() + { + return $this->definition['constraint'] || + $this->definition['onUpdate'] || + $this->definition['onDelete']; + } + + public function isDeferred() + { + return $this->definition['deferred']; + } + + public function isDeferrable() + { + return $this->definition['deferrable']; + } + public function isEqual() + { + return $this->definition['equal']; + } + + public function offsetExists($offset) + { + return isset($this->definition[$offset]); + } + + public function offsetGet($offset) + { + if (isset($this->definition[$offset])) { + return $this->definition[$offset]; + } + + return null; + } + + public function offsetSet($offset, $value) + { + if (isset($this->definition[$offset])) { + $this->definition[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + $this->definition[$offset] = false; + } + + public function toArray() + { + return $this->definition; + } + + final public function getAlias() + { + return $this->definition['alias']; + } + + final public function getType() + { + return $this->definition['type']; + } + + public function isCascadeDelete() + { + return in_array('delete', $this->definition['cascade']); + } + + final public function getTable() + { + return IPF_ORM_Manager::connection()->getTable($this->definition['class']); + } + + final public function getClass() + { + return $this->definition['class']; + } + + final public function getLocal() + { + return $this->definition['local']; + } + + final public function getLocalFieldName() + { + return $this->definition['localTable']->getFieldName($this->definition['local']); + } + + final public function getForeign() + { + return $this->definition['foreign']; + } + + final public function getForeignFieldName() + { + return $this->definition['table']->getFieldName($this->definition['foreign']); + } + + final public function isComposite() + { + return ($this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE || + $this->definition['type'] == IPF_ORM_Relation::MANY_COMPOSITE); + } + + final public function isOneToOne() + { + return ($this->definition['type'] == IPF_ORM_Relation::ONE_AGGREGATE || + $this->definition['type'] == IPF_ORM_Relation::ONE_COMPOSITE); + } + + abstract public function getRelationDql($count, $context); + + abstract public function fetchRelatedFor(IPF_ORM_Record $record); + + public function __toString() + { + $r[] = "
";
+        foreach ($this->definition as $k => $v) {
+            if (is_object($v)) {
+                $v = 'Object(' . get_class($v) . ')';
+            }
+            $r[] = $k . ' : ' . $v;
+        }
+        $r[] = "
"; + return implode("\n", $r); + } +} + diff --git a/src/orm/relation/association.php b/src/orm/relation/association.php new file mode 100644 index 0000000..29fc2b2 --- /dev/null +++ b/src/orm/relation/association.php @@ -0,0 +1,45 @@ +definition['refTable']; + } + public function getAssociationTable() + { + return $this->definition['refTable']; + } + + public function getRelationDql($count, $context) + { + $component = $this->definition['refTable']->getComponentName(); + $thisTable = $this->getTable()->getComponentName(); + + $sub = substr(str_repeat('?, ', $count), 0, -2); + switch ($context) { + case "record": + $dql = 'FROM ' . $thisTable . '.' . $component . + ' WHERE ' . $thisTable . '.' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')'; + break; + case "collection": + $dql = 'FROM ' . $component . '.' . $thisTable . + ' WHERE ' . $component . '.' . $this->definition['local'] . ' IN (' . $sub . ')'; + break; + } + return $dql; + } + + public function fetchRelatedFor(IPF_ORM_Record $record) + { + $id = $record->pk(); + if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { + $coll = new IPF_ORM_Collection($this->getTable()); + } else { + $coll = $this->getTable()->getConnection()->query($this->getRelationDql(1, 'record'), array($id)); + } + $coll->setReference($record, $this); + return $coll; + } +} + diff --git a/src/orm/relation/foreignkey.php b/src/orm/relation/foreignkey.php new file mode 100644 index 0000000..5e5a91f --- /dev/null +++ b/src/orm/relation/foreignkey.php @@ -0,0 +1,72 @@ +getTable(); + foreach ((array) $this->definition['local'] as $local) { + $value = $record->get($localTable->getFieldName($local)); + if (isset($value)) { + $id[] = $value; + } + } + if ($this->isOneToOne()) { + if ( ! $record->exists() || empty($id) || + ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { + + $related = $this->getTable()->create(); + } else { + $dql = 'FROM ' . $this->getTable()->getComponentName() + . ' WHERE ' . $this->getCondition(); + + $coll = $this->getTable()->getConnection()->query($dql, $id); + $related = $coll[0]; + } + + $related->set($related->getTable()->getFieldName($this->definition['foreign']), + $record, false); + } else { + + if ( ! $record->exists() || empty($id) || + ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { + + $related = new IPF_ORM_Collection($this->getTable()); + } else { + $query = $this->getRelationDql(1, ''); + $related = $this->getTable()->getConnection()->query($query, $id); + } + $related->setReference($record, $this); + } + return $related; + } + + public function getRelationDql($count, $context) + { + $table = $this->getTable(); + $component = $table->getComponentName(); + + $dql = 'FROM ' . $component + . ' WHERE ' . $component . '.' . $this->definition['foreign'] + . ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')'; + + $ordering = $table->getOrdering(); + if ($ordering) + $dql .= ' ORDER BY ' . implode(', ', $ordering); + + return $dql; + } + + public function getCondition($alias = null) + { + if ( ! $alias) { + $alias = $this->getTable()->getComponentName(); + } + $conditions = array(); + foreach ((array) $this->definition['foreign'] as $foreign) { + $conditions[] = $alias . '.' . $foreign . ' = ?'; + } + return implode(' AND ', $conditions); + } +} \ No newline at end of file diff --git a/src/orm/relation/localkey.php b/src/orm/relation/localkey.php new file mode 100644 index 0000000..edd225f --- /dev/null +++ b/src/orm/relation/localkey.php @@ -0,0 +1,47 @@ +getTable()->getFieldName($this->definition['local']); + $id = $record->get($localFieldName); + + if (is_null($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { + $related = $this->getTable()->create(); + } else { + $dql = 'FROM ' . $this->getTable()->getComponentName() + . ' WHERE ' . $this->getCondition(); + + $related = $this->getTable() + ->getConnection() + ->query($dql, array($id)) + ->getFirst(); + + if ( ! $related || empty($related)) { + $related = $this->getTable()->create(); + } + } + + $record->set($localFieldName, $related, false); + + return $related; + } + + public function getRelationDql($count, $context) + { + $component = $this->getTable()->getComponentName(); + return 'FROM ' . $component . + ' WHERE ' . $component . '.' . $this->definition['foreign'] . + ' IN (' . substr(str_repeat('?, ', $count), 0, -2) . ')'; + } + + public function getCondition($alias = null) + { + if ( ! $alias) { + $alias = $this->getTable()->getComponentName(); + } + return $alias . '.' . $this->definition['foreign'] . ' = ?'; + } +} + diff --git a/src/orm/relation/nest.php b/src/orm/relation/nest.php new file mode 100644 index 0000000..9e27245 --- /dev/null +++ b/src/orm/relation/nest.php @@ -0,0 +1,85 @@ +definition['table']->getIdentifierColumnNames(); + $identifier = array_pop($identifierColumnNames); + $sub = 'SELECT '.$this->definition['foreign'] + . ' FROM '.$this->definition['refTable']->getTableName() + . ' WHERE '.$this->definition['local'] + . ' = ?'; + + $sub2 = 'SELECT '.$this->definition['local'] + . ' FROM '.$this->definition['refTable']->getTableName() + . ' WHERE '.$this->definition['foreign'] + . ' = ?'; + + $dql = 'FROM ' . $this->definition['table']->getComponentName() + . '.' . $this->definition['refTable']->getComponentName() + . ' WHERE ' . $this->definition['table']->getComponentName() + . '.' . $identifier + . ' IN (' . $sub . ')' + . ' || ' . $this->definition['table']->getComponentName() + . '.' . $identifier + . ' IN (' . $sub2 . ')'; + break; + case 'collection': + $sub = substr(str_repeat('?, ', $count),0,-2); + $dql = 'FROM '.$this->definition['refTable']->getComponentName() + . '.' . $this->definition['table']->getComponentName() + . ' WHERE '.$this->definition['refTable']->getComponentName() + . '.' . $this->definition['local'] . ' IN (' . $sub . ')'; + }; + + return $dql; + } + + public function fetchRelatedFor(IPF_ORM_Record $record) + { + $id = $record->getIncremented(); + + + if (empty($id) || ! $this->definition['table']->getAttribute(IPF_ORM::ATTR_LOAD_REFERENCES)) { + return new IPF_ORM_Collection($this->getTable()); + } else { + $q = new IPF_ORM_RawSql($this->getTable()->getConnection()); + + $assocTable = $this->getAssociationFactory()->getTableName(); + $tableName = $record->getTable()->getTableName(); + $identifierColumnNames = $record->getTable()->getIdentifierColumnNames(); + $identifier = array_pop($identifierColumnNames); + + $sub = 'SELECT ' . $this->getForeign() + . ' FROM ' . $assocTable + . ' WHERE ' . $this->getLocal() + . ' = ?'; + + $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub . ')'; + $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getForeign(); + + if ($this->definition['equal']) { + $sub2 = 'SELECT ' . $this->getLocal() + . ' FROM ' . $assocTable + . ' WHERE ' . $this->getForeign() + . ' = ?'; + + $condition[] = $tableName . '.' . $identifier . ' IN (' . $sub2 . ')'; + $joinCondition[] = $tableName . '.' . $identifier . ' = ' . $assocTable . '.' . $this->getLocal(); + } + $q->select('{'.$tableName.'.*}') + ->addSelect('{'.$assocTable.'.*}') + ->from($tableName . ' INNER JOIN ' . $assocTable . ' ON ' . implode(' OR ', $joinCondition)) + ->where(implode(' OR ', $condition)); + $q->addComponent($tableName, $record->getTable()->getComponentName()); + $q->addComponent($assocTable, $record->getTable()->getComponentName(). '.' . $this->getAssociationFactory()->getComponentName()); + + $params = ($this->definition['equal']) ? array($id, $id) : array($id); + + return $q->execute($params); + } + } +} diff --git a/src/orm/relation/parser.php b/src/orm/relation/parser.php new file mode 100644 index 0000000..ce1fe89 --- /dev/null +++ b/src/orm/relation/parser.php @@ -0,0 +1,362 @@ +_table = $table; + } + + public function getTable() + { + return $this->_table; + } + + public function getPendingRelation($name) + { + if ( ! isset($this->_pending[$name])) { + throw new IPF_ORM_Exception('Unknown pending relation ' . $name); + } + + return $this->_pending[$name]; + } + + public function getPendingRelations() + { + return $this->_pending; + } + + public function unsetPendingRelations($name) + { + unset($this->_pending[$name]); + } + + public function hasRelation($name) + { + return isset($this->_pending[$name]) || isset($this->_relations[$name]); + } + + public function bind($name, $alias, $type, $options=array()) + { + if (!$alias) + $alias = $name; + + unset($this->relations[$alias]); + + $options['class'] = $name; + $options['alias'] = $alias; + $options['type'] = $type; + + $this->_pending[$alias] = $options; + } + + private function createRelation($alias) + { + if (isset($this->_relations[$alias])) + return; + + if (!isset($this->_pending[$alias])) + throw new IPF_ORM_Exception('Unknown relation alias "' . $alias . '".'); + + $def = $this->_pending[$alias]; + $identifierColumnNames = $this->_table->getIdentifierColumnNames(); + $idColumnName = array_pop($identifierColumnNames); + + // check if reference class name exists + // if it does we are dealing with association relation + if (isset($def['refClass'])) { + $def = $this->completeAssocDefinition($def); + $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName())); + + if ( ! isset($this->_pending[$def['refClass']]) && + ! isset($this->_relations[$def['refClass']])) { + + $parser = $def['refTable']->getRelationParser(); + if ( ! $parser->hasRelation($this->_table->getComponentName())) { + $parser->bind($this->_table->getComponentName(), null, IPF_ORM_Relation::ONE, array( + 'local' => $def['local'], + 'foreign' => $idColumnName, + 'localKey' => true, + )); + } + + if (!$this->hasRelation($def['refClass'])) { + $this->bind($def['refClass'], null, IPF_ORM_Relation::MANY, array( + 'foreign' => $def['local'], + 'local' => $idColumnName, + )); + } + } + if (in_array($def['class'], $localClasses)) { + $rel = new IPF_ORM_Relation_Nest($def); + } else { + $rel = new IPF_ORM_Relation_Association($def); + } + } else { + // simple foreign key relation + $def = $this->completeDefinition($def); + + if (isset($def['localKey'])) { + $rel = new IPF_ORM_Relation_LocalKey($def); + + // Automatically index foreign keys which are not primary + $foreign = (array) $def['foreign']; + foreach ($foreign as $fk) { + if ( ! $rel->getTable()->isIdentifier($fk)) { + $rel->getTable()->addIndex($fk, array('fields' => array($fk))); + } + } + } else { + $rel = new IPF_ORM_Relation_ForeignKey($def); + } + } + + if (isset($rel)) { + // unset pending relation + unset($this->_pending[$alias]); + + $this->_relations[$alias] = $rel; + } + } + + public function getRelation($alias) + { + $this->getRelations(); + return $this->_relations[$alias]; + } + + public function getRelations() + { + foreach ($this->_pending as $k => $v) { + $this->createRelation($k); + } + + return $this->_relations; + } + + public function completeAssocDefinition($def) + { + $conn = $this->_table->getConnection(); + + $def['table'] = IPF_ORM::getTable($def['class']); + $def['localTable'] = $this->_table; + $def['class'] = $def['table']->getComponentName(); + $def['refTable'] = IPF_ORM::getTable($def['refClass']); + + $id = $def['refTable']->getIdentifierColumnNames(); + + if (count($id) > 1) { + if ( ! isset($def['foreign'])) { + // foreign key not set + // try to guess the foreign key + + $def['foreign'] = ($def['local'] === $id[0]) ? $id[1] : $id[0]; + } + if ( ! isset($def['local'])) { + // foreign key not set + // try to guess the foreign key + + $def['local'] = ($def['foreign'] === $id[0]) ? $id[1] : $id[0]; + } + } else { + + if ( ! isset($def['foreign'])) { + // foreign key not set + // try to guess the foreign key + + $columns = $this->getIdentifiers($def['table']); + + $def['foreign'] = $columns; + } + if ( ! isset($def['local'])) { + // local key not set + // try to guess the local key + $columns = $this->getIdentifiers($this->_table); + + $def['local'] = $columns; + } + } + return $def; + } + + public function getIdentifiers(IPF_ORM_Table $table) + { + $componentNameToLower = strtolower($table->getComponentName()); + if (is_array($table->getIdentifier())) { + $columns = array(); + foreach ((array) $table->getIdentifierColumnNames() as $identColName) { + $columns[] = $componentNameToLower . '_' . $identColName; + } + } else { + $columns = $componentNameToLower . '_' . $table->getColumnName( + $table->getIdentifier()); + } + + return $columns; + } + + public function guessColumns(array $classes, IPF_ORM_Table $foreignTable) + { + $conn = $this->_table->getConnection(); + + foreach ($classes as $class) { + try { + $table = $conn->getTable($class); + } catch (IPF_ORM_Exception $e) { + continue; + } + $columns = $this->getIdentifiers($table); + $found = true; + + foreach ((array) $columns as $column) { + if ( ! $foreignTable->hasColumn($column)) { + $found = false; + break; + } + } + if ($found) { + break; + } + } + + if ( ! $found) { + throw new IPF_ORM_Exception("Couldn't find columns."); + } + + return $columns; + } + + public function completeDefinition($def) + { + $conn = $this->_table->getConnection(); + $def['table'] = IPF_ORM::getTable($def['class']); + $def['localTable'] = $this->_table; + $def['class'] = $def['table']->getComponentName(); + + $foreignClasses = array_merge($def['table']->getOption('parents'), array($def['class'])); + $localClasses = array_merge($this->_table->getOption('parents'), array($this->_table->getComponentName())); + + $localIdentifierColumnNames = $this->_table->getIdentifierColumnNames(); + $localIdentifierCount = count($localIdentifierColumnNames); + $localIdColumnName = array_pop($localIdentifierColumnNames); + $foreignIdentifierColumnNames = $def['table']->getIdentifierColumnNames(); + $foreignIdColumnName = array_pop($foreignIdentifierColumnNames); + + if (isset($def['local'])) { + if ( ! isset($def['foreign'])) { + // local key is set, but foreign key is not + // try to guess the foreign key + + if ($def['local'] === $localIdColumnName) { + $def['foreign'] = $this->guessColumns($localClasses, $def['table']); + } else { + // the foreign field is likely to be the + // identifier of the foreign class + $def['foreign'] = $foreignIdColumnName; + $def['localKey'] = true; + } + } else { + if ($localIdentifierCount == 1) { + if ($def['local'] == $localIdColumnName && isset($def['owningSide']) + && $def['owningSide'] === true) { + $def['localKey'] = true; + } else if (($def['local'] !== $localIdColumnName && $def['type'] == IPF_ORM_Relation::ONE)) { + $def['localKey'] = true; + } + } else if ($localIdentifierCount > 1) { + // It's a composite key and since 'foreign' can not point to a composite + // key currently, we know that 'local' must be the foreign key. + $def['localKey'] = true; + } + } + } else { + if (isset($def['foreign'])) { + // local key not set, but foreign key is set + // try to guess the local key + if ($def['foreign'] === $foreignIdColumnName) { + $def['localKey'] = true; + try { + $def['local'] = $this->guessColumns($foreignClasses, $this->_table); + } catch (IPF_ORM_Exception $e) { + $def['local'] = $localIdColumnName; + } + } else { + $def['local'] = $localIdColumnName; + } + } else { + // neither local or foreign key is being set + // try to guess both keys + + $conn = $this->_table->getConnection(); + + // the following loops are needed for covering inheritance + foreach ($localClasses as $class) { + $table = $conn->getTable($class); + $identifierColumnNames = $table->getIdentifierColumnNames(); + $idColumnName = array_pop($identifierColumnNames); + $column = strtolower($table->getComponentName()) + . '_' . $idColumnName; + + foreach ($foreignClasses as $class2) { + $table2 = $conn->getTable($class2); + if ($table2->hasColumn($column)) { + $def['foreign'] = $column; + $def['local'] = $idColumnName; + return $def; + } + } + } + + foreach ($foreignClasses as $class) { + $table = $conn->getTable($class); + $identifierColumnNames = $table->getIdentifierColumnNames(); + $idColumnName = array_pop($identifierColumnNames); + $column = strtolower($table->getComponentName()) + . '_' . $idColumnName; + + foreach ($localClasses as $class2) { + $table2 = $conn->getTable($class2); + if ($table2->hasColumn($column)) { + $def['foreign'] = $idColumnName; + $def['local'] = $column; + $def['localKey'] = true; + return $def; + } + } + } + + // auto-add columns and auto-build relation + $columns = array(); + foreach ((array) $this->_table->getIdentifierColumnNames() as $id) { + // ?? should this not be $this->_table->getComponentName() ?? + $column = strtolower($table->getComponentName()) + . '_' . $id; + + $col = $this->_table->getColumnDefinition($id); + $type = $col['type']; + $length = $col['length']; + + unset($col['type']); + unset($col['length']); + unset($col['autoincrement']); + unset($col['primary']); + + $def['table']->setColumn($column, $type, $length, $col); + + $columns[] = $column; + } + if (count($columns) > 1) { + $def['foreign'] = $columns; + } else { + $def['foreign'] = $columns[0]; + } + $def['local'] = $localIdColumnName; + } + } + return $def; + } +} diff --git a/src/orm/table.php b/src/orm/table.php new file mode 100644 index 0000000..6208103 --- /dev/null +++ b/src/orm/table.php @@ -0,0 +1,1033 @@ + null, + 'tableName' => null, + 'inheritanceMap' => array(), + 'enumMap' => array(), + 'type' => null, + 'charset' => null, + 'collation' => null, + 'indexes' => array(), + 'parents' => array(), + 'queryParts' => array(), + 'versioning' => null, + 'subclasses' => array(), + ); + + protected $_ordering = null; + + protected $_parser; + + protected $_templates = array(); + protected $_invokedMethods = array(); + + public $listeners = array(); + + public function __construct($name, IPF_ORM_Connection $conn) + { + if (empty($name) || !class_exists($name)) + throw new IPF_ORM_Exception("Couldn't find class " . $name); + + $this->_conn = $conn; + $this->setParent($this->_conn); + + $this->_options['name'] = $name; + $this->_parser = new IPF_ORM_Relation_Parser($this); + + $this->initParents($name); + + // create database table + $name::setTableDefinition($this); + + if (!isset($this->_options['tableName'])) { + $this->setTableName(IPF_ORM_Inflector::tableize($class->getName())); + } + + $this->initIdentifier(); + + $name::setUp($this); + + $this->_repository = new IPF_ORM_Table_Repository($this); + } + + private function initParents($name) + { + $names = array(); + + $class = $name; + do { + if ($class === 'IPF_ORM_Record') + break; + + $name = $class; + $names[] = $name; + } while ($class = get_parent_class($class)); + + if ($class === false) + throw new IPF_ORM_Exception('Class "' . $name . '" must be a child class of IPF_ORM_Record'); + + // reverse names + $names = array_reverse($names); + // save parents + array_pop($names); + $this->_options['parents'] = $names; + } + + public function initIdentifier() + { + switch (count($this->_identifier)) { + case 0: + $definition = array('type' => 'integer', + 'length' => 20, + 'autoincrement' => true, + 'primary' => true); + $this->setColumn('id', $definition['type'], $definition['length'], $definition, true); + $this->_identifier = 'id'; + $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC; + break; + case 1: + foreach ($this->_identifier as $pk) { + $e = $this->getDefinitionOf($pk); + + $found = false; + + foreach ($e as $option => $value) { + if ($found) { + break; + } + + $e2 = explode(':', $option); + + switch (strtolower($e2[0])) { + case 'autoincrement': + case 'autoinc': + if ($value !== false) { + $this->_identifierType = IPF_ORM::IDENTIFIER_AUTOINC; + $found = true; + } + break; + } + } + if ( ! isset($this->_identifierType)) { + $this->_identifierType = IPF_ORM::IDENTIFIER_NATURAL; + } + } + + $this->_identifier = $pk; + + break; + default: + $this->_identifierType = IPF_ORM::IDENTIFIER_COMPOSITE; + } + } + + public function isIdentifier($fieldName) + { + return ($fieldName === $this->getIdentifier() || + in_array($fieldName, (array) $this->getIdentifier())); + } + + public function isIdentifierAutoincrement() + { + return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_AUTOINC; + } + + public function isIdentifierComposite() + { + return $this->getIdentifierType() === IPF_ORM::IDENTIFIER_COMPOSITE; + } + + public function getMethodOwner($method) + { + return (isset($this->_invokedMethods[$method])) ? + $this->_invokedMethods[$method] : false; + } + + public function setMethodOwner($method, $class) + { + $this->_invokedMethods[$method] = $class; + } + + public function getTemplates() + { + return $this->_templates; + } + + public function getRelationParser() + { + return $this->_parser; + } + + public function __get($option) + { + if (isset($this->_options[$option])) { + return $this->_options[$option]; + } + return null; + } + + public function __isset($option) + { + return isset($this->_options[$option]); + } + + public function getOptions() + { + return $this->_options; + } + + public function setOptions($options) + { + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + } + + public function addCheckConstraint($definition, $name) + { + if (is_string($name)) { + $this->_options['checks'][$name] = $definition; + } else { + $this->_options['checks'][] = $definition; + } + + return $this; + } + + public function addIndex($index, array $definition) + { + $this->_options['indexes'][$index] = $definition; + } + + public function getIndex($index) + { + if (isset($this->_options['indexes'][$index])) { + return $this->_options['indexes'][$index]; + } + + return false; + } + + public function bind($class, $alias, $type, array $options) + { + $this->_parser->bind($class, $alias, $type, $options); + } + + public function ownsOne($class, $alias, $options=array()) + { + $this->bind($class, $alias, IPF_ORM_Relation::ONE_COMPOSITE, $options); + } + + public function ownsMany($class, $alias, $options=array()) + { + $this->bind($class, $alias, IPF_ORM_Relation::MANY_COMPOSITE, $options); + } + + public function hasOne($class, $alias, $options=array()) + { + $this->bind($class, $alias, IPF_ORM_Relation::ONE_AGGREGATE, $options); + } + + public function hasMany($class, $alias, $options=array()) + { + $this->bind($class, $alias, IPF_ORM_Relation::MANY_AGGREGATE, $options); + } + + public function hasRelation($alias) + { + return $this->_parser->hasRelation($alias); + } + + public function getRelation($alias) + { + return $this->_parser->getRelation($alias); + } + + public function getRelations() + { + return $this->_parser->getRelations(); + } + + public function createQuery($alias = '') + { + if ( ! empty($alias)) { + $alias = ' ' . trim($alias); + } + return IPF_ORM_Query::create($this->_conn)->from($this->getComponentName() . $alias); + } + + public function getRepository() + { + return $this->_repository; + } + + public function setOption($name, $value) + { + switch ($name) { + case 'name': + case 'tableName': + break; + case 'enumMap': + case 'inheritanceMap': + case 'index': + if (!is_array($value)) + throw new IPF_ORM_Exception($name . ' should be an array.'); + break; + } + $this->_options[$name] = $value; + } + + public function getOption($name, $default=null) + { + if (isset($this->_options[$name])) { + return $this->_options[$name]; + } + return $default; + } + + public function getColumnName($fieldName) + { + // FIX ME: This is being used in places where an array is passed, but it should not be an array + // For example in places where IPF_ORM should support composite foreign/primary keys + $fieldName = is_array($fieldName) ? $fieldName[0]:$fieldName; + + if (isset($this->_columnNames[$fieldName])) { + return $this->_columnNames[$fieldName]; + } + + return strtolower($fieldName); + } + + public function getColumnDefinition($columnName, $default=false) + { + if (isset($this->_columns[$columnName])) { + return $this->_columns[$columnName]; + } + return false; + } + + public function getFieldName($columnName) + { + if (isset($this->_fieldNames[$columnName])) { + return $this->_fieldNames[$columnName]; + } + return $columnName; + } + public function setColumns(array $definitions) + { + foreach ($definitions as $name => $options) { + $this->setColumn($name, $options['type'], $options['length'], $options); + } + } + + public function setColumn($name, $type, $length = null, $options = array(), $prepend = false) + { + if (is_string($options)) { + $options = explode('|', $options); + } + + foreach ($options as $k => $option) { + if (is_numeric($k)) { + if (!empty($option)) { + $options[$option] = true; + } + unset($options[$k]); + } + } + + // extract column name & field name + if (stripos($name, ' as ')) + { + if (strpos($name, ' as')) { + $parts = explode(' as ', $name); + } else { + $parts = explode(' AS ', $name); + } + + if (count($parts) > 1) { + $fieldName = $parts[1]; + } else { + $fieldName = $parts[0]; + } + + $name = strtolower($parts[0]); + } else { + $fieldName = $name; + $name = strtolower($name); + } + + $name = trim($name); + $fieldName = trim($fieldName); + + if ($prepend) { + $this->_columnNames = array_merge(array($fieldName => $name), $this->_columnNames); + $this->_fieldNames = array_merge(array($name => $fieldName), $this->_fieldNames); + } else { + $this->_columnNames[$fieldName] = $name; + $this->_fieldNames[$name] = $fieldName; + } + + if ($length == null) { + switch ($type) { + case 'string': + case 'clob': + case 'float': + case 'double': + case 'integer': + case 'array': + case 'object': + case 'blob': + case 'gzip': + // use php int max + $length = 2147483647; + break; + case 'boolean': + $length = 1; + case 'date': + // YYYY-MM-DD ISO 8601 + $length = 10; + case 'time': + // HH:NN:SS+00:00 ISO 8601 + $length = 14; + case 'timestamp': + // YYYY-MM-DDTHH:MM:SS+00:00 ISO 8601 + $length = 25; + break; + } + } + + $options['type'] = $type; + $options['length'] = $length; + + if ($prepend) { + $this->_columns = array_merge(array($name => $options), $this->_columns); + } else { + $this->_columns[$name] = $options; + } + + if (isset($options['primary']) && $options['primary']) { + if (isset($this->_identifier)) { + $this->_identifier = (array) $this->_identifier; + } + if ( ! in_array($fieldName, $this->_identifier)) { + $this->_identifier[] = $fieldName; + } + } + if (isset($options['default'])) { + $this->hasDefaultValues = true; + } + } + + public function hasDefaultValues() + { + return $this->hasDefaultValues; + } + + public function getDefaultValueOf($fieldName) + { + $columnName = $this->getColumnName($fieldName); + if ( ! isset($this->_columns[$columnName])) { + throw new IPF_ORM_Exception("Couldn't get default value. Column ".$columnName." doesn't exist."); + } + if (isset($this->_columns[$columnName]['default'])) { + return $this->_columns[$columnName]['default']; + } else { + return null; + } + } + + public function getIdentifier() + { + return $this->_identifier; + } + + public function getIdentifierType() + { + return $this->_identifierType; + } + + public function hasColumn($columnName) + { + return isset($this->_columns[strtolower($columnName)]); + } + + public function hasField($fieldName) + { + return isset($this->_columnNames[$fieldName]); + } + + public function setSubClasses(array $map) + { + $class = $this->getComponentName(); + if (isset($map[$class])) { + $this->setOption('inheritanceMap', $map[$class]); + } else { + $this->setOption('subclasses', array_keys($map)); + } + } + + public function setConnection(IPF_ORM_Connection $conn) + { + $this->_conn = $conn; + + $this->setParent($this->_conn); + + return $this; + } + + public function getConnection() + { + return $this->_conn; + } + + public function create(array $array = array()) + { + $record = new $this->_options['name']($this, true); + $record->fromArray($array); + + return $record; + } + + public function find($id, $hydrationMode = null) + { + if (is_null($id)) { + return false; + } + + $id = is_array($id) ? array_values($id) : array($id); + + $q = $this->createQuery(); + $q->where(implode(' = ? AND ', (array) $this->getIdentifier()) . ' = ?', $id) + ->limit(1); + $res = $q->fetchOne(array(), $hydrationMode); + $q->free(); + + return $res; + } + + public function findAll($hydrationMode = null) + { + $q = $this->createQuery(); + if ($this->_ordering) { + $q->orderBy(implode(', ', $this->_ordering)); + } elseif ($this->hasTemplate('IPF_ORM_Template_Orderable')) { + $q->orderBy($this->getTemplate('IPF_ORM_Template_Orderable')->getColumnName()); + } + return $q->execute(array(), $hydrationMode); + } + + public function findBySql($dql, $params = array(), $hydrationMode = null) + { + return $this->createQuery()->where($dql)->execute($params, $hydrationMode); + } + + public function findByDql($dql, $params = array(), $hydrationMode = null) + { + $parser = new IPF_ORM_Query($this->_conn); + $component = $this->getComponentName(); + $query = 'FROM ' . $component . ' WHERE ' . $dql; + + return $parser->query($query, $params, $hydrationMode); + } + + public function findOneByDql($dql, $params = array(), $hydrationMode = null) + { + $results = $this->findByDql($dql, $params, $hydrationMode); + if (is_array($results) && isset($results[0])) { + return $results[0]; + } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) { + return $results->getFirst(); + } else { + return false; + } + } + + + public function execute($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD) + { + return IPF_ORM_Manager::getInstance() + ->getQueryRegistry() + ->get($queryKey, $this->getComponentName()) + ->execute($params, $hydrationMode); + } + + public function executeOne($queryKey, $params = array(), $hydrationMode = IPF_ORM::HYDRATE_RECORD) + { + return IPF_ORM_Manager::getInstance() + ->getQueryRegistry() + ->get($queryKey, $this->getComponentName()) + ->fetchOne($params, $hydrationMode); + } + + public function clear() + { + $this->_identityMap = array(); + } + + public function addRecord(IPF_ORM_Record $record) + { + $id = implode(' ', $record->identifier()); + + if (isset($this->_identityMap[$id])) { + return false; + } + + $this->_identityMap[$id] = $record; + + return true; + } + + public function removeRecord(IPF_ORM_Record $record) + { + $id = implode(' ', $record->identifier()); + + if (isset($this->_identityMap[$id])) { + unset($this->_identityMap[$id]); + return true; + } + + return false; + } + + public function getRecord() + { + if ( ! empty($this->_data)) { + $identifierFieldNames = $this->getIdentifier(); + + if ( ! is_array($identifierFieldNames)) { + $identifierFieldNames = array($identifierFieldNames); + } + + $found = false; + foreach ($identifierFieldNames as $fieldName) { + if ( ! isset($this->_data[$fieldName])) { + // primary key column not found return new record + $found = true; + break; + } + $id[] = $this->_data[$fieldName]; + } + + if ($found) { + $recordName = $this->getComponentName(); + $record = new $recordName($this, true); + $this->_data = array(); + return $record; + } + + + $id = implode(' ', $id); + + if (isset($this->_identityMap[$id])) { + $record = $this->_identityMap[$id]; + $record->hydrate($this->_data); + } else { + $recordName = $this->getComponentName(); + $record = new $recordName($this); + $this->_identityMap[$id] = $record; + } + $this->_data = array(); + } else { + $recordName = $this->getComponentName(); + $record = new $recordName($this, true); + } + + return $record; + } + + final public function applyInheritance($where) + { + if ( ! empty($this->_options['inheritanceMap'])) { + $a = array(); + foreach ($this->_options['inheritanceMap'] as $field => $value) { + $a[] = $this->getColumnName($field) . ' = ?'; + } + $i = implode(' AND ', $a); + $where .= ' AND ' . $i; + } + return $where; + } + + public function count() + { + $a = $this->_conn->execute('SELECT COUNT(1) FROM ' . $this->_options['tableName'])->fetch(IPF_ORM::FETCH_NUM); + return current($a); + } + + public function getQueryObject() + { + $graph = new IPF_ORM_Query($this->getConnection()); + $graph->load($this->getComponentName()); + return $graph; + } + + public function getEnumValues($fieldName) + { + $columnName = $this->getColumnName($fieldName); + if (isset($this->_columns[$columnName]['values'])) { + return $this->_columns[$columnName]['values']; + } else { + return array(); + } + } + + public function enumValue($fieldName, $index) + { + if (IPF_ORM_Null::isNull($index)) { + return $index; + } + + $columnName = $this->getColumnName($fieldName); + if ( ! $this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM) + && isset($this->_columns[$columnName]['values'][$index]) + ) { + return $this->_columns[$columnName]['values'][$index]; + } + + return $index; + } + + public function enumIndex($fieldName, $value) + { + $values = $this->getEnumValues($fieldName); + + $index = array_search($value, $values); + if ($index === false || !$this->_conn->getAttribute(IPF_ORM::ATTR_USE_NATIVE_ENUM)) { + return $index; + } + return $value; + } + + public function getColumnCount() + { + return count($this->_columns); + } + + public function getColumns() + { + return $this->_columns; + } + + public function removeColumn($fieldName) + { + if ($this->hasField($fieldName)) { + $columnName = $this->getColumnName($fieldName); + unset($this->_columnNames[$fieldName], $this->_fieldNames[$columnName], $this->_columns[$columnName]); + return true; + } else { + return false; + } + } + + public function getColumnNames(array $fieldNames = null) + { + if ($fieldNames === null) { + return array_keys($this->_columns); + } else { + $columnNames = array(); + foreach ($fieldNames as $fieldName) { + $columnNames[] = $this->getColumnName($fieldName); + } + return $columnNames; + } + } + + public function getIdentifierColumnNames() + { + return $this->getColumnNames((array) $this->getIdentifier()); + } + + public function getFieldNames() + { + return array_values($this->_fieldNames); + } + + public function getDefinitionOf($fieldName) + { + $columnName = $this->getColumnName($fieldName); + return $this->getColumnDefinition($columnName); + } + + public function getTypeOf($fieldName) + { + return $this->getTypeOfColumn($this->getColumnName($fieldName)); + } + + public function getTypeOfColumn($columnName) + { + return isset($this->_columns[$columnName]) ? $this->_columns[$columnName]['type'] : false; + } + + public function setData(array $data) + { + $this->_data = $data; + } + + public function getData() + { + return $this->_data; + } + + public function prepareValue($fieldName, $value, $typeHint = null) + { + if (IPF_ORM_Null::isNull($value)) { + return $value; + } else if ($value === null) { + return null; + } else { + $type = is_null($typeHint) ? $this->getTypeOf($fieldName) : $typeHint; + + switch ($type) { + case 'integer': + case 'string'; + // don't do any casting here PHP INT_MAX is smaller than what the databases support + break; + case 'enum': + return $this->enumValue($fieldName, $value); + break; + case 'boolean': + return (boolean) $value; + break; + case 'array': + case 'object': + if (is_string($value)) { + $value = empty($value) ? null:unserialize($value); + + if ($value === false) { + throw new IPF_ORM_Exception('Unserialization of ' . $fieldName . ' failed.'); + } + return $value; + } + break; + case 'gzip': + $value = gzuncompress($value); + + if ($value === false) { + throw new IPF_ORM_Exception('Uncompressing of ' . $fieldName . ' failed.'); + } + return $value; + break; + } + } + return $value; + } + + public function getComponentName() + { + return $this->_options['name']; + } + + public function getTableName() + { + return $this->_options['tableName']; + } + + public function setTableName($tableName) + { + $this->setOption('tableName', $tableName); + } + + public function getTemplate($template) + { + if (!isset($this->_templates[$template])) + throw new IPF_ORM_Exception('Template ' . $template . ' not loaded'); + return $this->_templates[$template]; + } + + public function hasTemplate($template) + { + return isset($this->_templates[$template]); + } + + public function addTemplate($tpl, array $options=array()) + { + if (!is_object($tpl)) { + $className = 'IPF_ORM_Template_' . $tpl; + + if (class_exists($className, true)) { + $tpl = new $className($options); + } else if (class_exists($tpl, true)) { + $tpl = new $tpl($options); + } else { + throw new IPF_ORM_Record_Exception('Could not load behavior named: "' . $tpl . '"'); + } + } + + if (!($tpl instanceof IPF_ORM_Template)) { + throw new IPF_ORM_Record_Exception('Loaded behavior class is not an istance of IPF_ORM_Template.'); + } + + $className = get_class($tpl); + $this->_templates[$className] = $tpl; + + $tpl->setTableDefinition($this); + } + + public function setOrdering($ordering) + { + $this->_ordering = $ordering; + } + + public function getOrdering() + { + return $this->_ordering; + } + + public function bindQueryParts(array $queryParts) + { + $this->_options['queryParts'] = $queryParts; + return $this; + } + + public function bindQueryPart($queryPart, $value) + { + $this->_options['queryParts'][$queryPart] = $value; + return $this; + } + + public function getFieldValidators($fieldName) + { + $validators = array(); + $columnName = $this->getColumnName($fieldName); + // this loop is a dirty workaround to get the validators filtered out of + // the options, since everything is squeezed together currently + + if (!isset($this->_columns[$columnName])) + return array(); + + foreach ($this->_columns[$columnName] as $name => $args) { + if (empty($name) + || $name == 'primary' + || $name == 'protected' + || $name == 'autoincrement' + || $name == 'default' + || $name == 'values' + || $name == 'zerofill' + || $name == 'owner' + || $name == 'scale' + || $name == 'type' + || $name == 'length' + || $name == 'fixed') { + continue; + } + if ($name == 'notnull' && isset($this->_columns[$columnName]['autoincrement'])) { + continue; + } + // skip it if it's explicitly set to FALSE (i.e. notnull => false) + if ($args === false) { + continue; + } + $validators[$name] = $args; + } + + return $validators; + } + + public function getBoundQueryPart($queryPart) + { + if (isset($this->_options['queryParts'][$queryPart])) + return $this->_options['queryParts'][$queryPart]; + else + return null; + } + + public function __toString() + { + return IPF_ORM_Utils::getTableAsString($this); + } + + protected function findBy($fieldName, $value, $hydrationMode = null) + { + $q = $this->createQuery()->where($fieldName . ' = ?', array($value)); + if ($this->_ordering) + $q->orderBy(implode(', ', $this->_ordering)); + return $q->execute(array(), $hydrationMode); + } + + protected function findOneBy($fieldName, $value, $hydrationMode = null) + { + $results = $this->createQuery() + ->where($fieldName . ' = ?',array($value)) + ->limit(1) + ->execute(array(), $hydrationMode); + + if (is_array($results) && isset($results[0])) { + return $results[0]; + } else if ($results instanceof IPF_ORM_Collection && $results->count() > 0) { + return $results->getFirst(); + } else { + return false; + } + } + + protected function _resolveFindByFieldName($name) + { + $fieldName = IPF_ORM_Inflector::tableize($name); + if ($this->hasColumn($name) || $this->hasField($name)) { + return $this->getFieldName($this->getColumnName($name)); + } else if ($this->hasColumn($fieldName) || $this->hasField($fieldName)) { + return $this->getFieldName($this->getColumnName($fieldName)); + } else { + return false; + } + } + + public function __call($method, $arguments) + { + if (substr($method, 0, 6) == 'findBy') { + $by = substr($method, 6, strlen($method)); + $method = 'findBy'; + } else if (substr($method, 0, 9) == 'findOneBy') { + $by = substr($method, 9, strlen($method)); + $method = 'findOneBy'; + } + + if (isset($by)) { + if ( ! isset($arguments[0])) { + throw new IPF_ORM_Exception('You must specify the value to findBy'); + } + + $fieldName = $this->_resolveFindByFieldName($by); + $hydrationMode = isset($arguments[1]) ? $arguments[1]:null; + if ($this->hasField($fieldName)) { + return $this->$method($fieldName, $arguments[0], $hydrationMode); + } else if ($this->hasRelation($by)) { + $relation = $this->getRelation($by); + + if ($relation['type'] === IPF_ORM_Relation::MANY) { + throw new IPF_ORM_Exception('Cannot findBy many relationship.'); + } + + return $this->$method($relation['local'], $arguments[0], $hydrationMode); + } else { + throw new IPF_ORM_Exception('Cannot find by: ' . $by . '. Invalid column or relationship alias.'); + } + } + + throw new IPF_ORM_Exception(sprintf('Unknown method %s::%s', get_class($this), $method)); + } + + public function notifyRecordListeners($method, $event) + { + foreach ($this->listeners as $listener) + if (is_callable(array($listener, $method))) + $listener->$method($event); + } +} + diff --git a/src/orm/table/repository.php b/src/orm/table/repository.php new file mode 100644 index 0000000..1fd1557 --- /dev/null +++ b/src/orm/table/repository.php @@ -0,0 +1,77 @@ +table = $table; + } + + public function getTable() + { + return $this->table; + } + + public function add(IPF_ORM_Record $record) + { + $oid = $record->getOID(); + + if (isset($this->registry[$oid])) { + return false; + } + $this->registry[$oid] = $record; + + return true; + } + + public function get($oid) + { + if ( ! isset($this->registry[$oid])) { + throw new IPF_ORM_Exception("Unknown object identifier"); + } + return $this->registry[$oid]; + } + + public function count() + { + return count($this->registry); + } + + public function evict($oid) + { + if ( ! isset($this->registry[$oid])) { + return false; + } + unset($this->registry[$oid]); + return true; + } + + public function evictAll() + { + $evicted = 0; + foreach ($this->registry as $oid=>$record) { + if ($this->evict($oid)) { + $evicted++; + } + } + return $evicted; + } + + public function getIterator() + { + return new ArrayIterator($this->registry); + } + + public function contains($oid) + { + return isset($this->registry[$oid]); + } + + public function loadAll() + { + $this->table->findAll(); + } +} \ No newline at end of file diff --git a/src/orm/template.php b/src/orm/template.php new file mode 100644 index 0000000..376a859 --- /dev/null +++ b/src/orm/template.php @@ -0,0 +1,19 @@ +_invoker = $invoker; + } + + public function getInvoker() + { + return $this->_invoker; + } + + abstract public function setTableDefinition(IPF_ORM_Table $table); +} + diff --git a/src/orm/template/listener/orderable.php b/src/orm/template/listener/orderable.php new file mode 100644 index 0000000..157f492 --- /dev/null +++ b/src/orm/template/listener/orderable.php @@ -0,0 +1,45 @@ +columnName = $columnName; + $this->prepend = $prepend; + } + + public function preInsert(IPF_ORM_Event $event) + { + $this->setOrderValue($event->getInvoker()); + } + + public function preUpdate(IPF_ORM_Event $event) + { + $this->setOrderValue($event->getInvoker()); + } + + private function setOrderValue($obj) + { + $columnName = $this->columnName; + if ($obj->$columnName !== null) + return; + + if ($this->prepend) { + $f = 'min'; + $d = '-'; + } else { + $f = 'max'; + $d = '+'; + } + + $res = IPF_ORM_Query::create() + ->select('coalesce('.$f.'('.$this->columnName.') '.$d.' 1, 1) as x_ord') + ->from(get_class($obj)) + ->execute(); + + $obj->$columnName = (int)$res[0]->x_ord; + } +} + diff --git a/src/orm/template/listener/owned.php b/src/orm/template/listener/owned.php new file mode 100644 index 0000000..7c99b69 --- /dev/null +++ b/src/orm/template/listener/owned.php @@ -0,0 +1,34 @@ +columnName = $columnName; + } + + public function preInsert(IPF_ORM_Event $event) + { + $this->setOwner($event->getInvoker()); + } + + public function preUpdate(IPF_ORM_Event $event) + { + $this->setOwner($event->getInvoker()); + } + + private function setOwner($obj) + { + $columnName = $this->columnName; + if ($obj->$columnName) + return; + + $request = IPF_Project::getInstance()->request; + if ($request && !$request->user->isAnonymous()) { + $obj->$columnName = $request->user->id; + } + } +} + diff --git a/src/orm/template/listener/sluggable.php b/src/orm/template/listener/sluggable.php new file mode 100644 index 0000000..eaf0735 --- /dev/null +++ b/src/orm/template/listener/sluggable.php @@ -0,0 +1,113 @@ +_options = $options; + } + + public function preInsert(IPF_ORM_Event $event) + { + $name = $this->_options['name']; + + $record = $event->getInvoker(); + + if ( ! $record->$name) { + $record->$name = $this->buildSlug($record); + } + } + + public function preUpdate(IPF_ORM_Event $event) + { + if (false !== $this->_options['unique']) { + $name = $this->_options['name']; + + $record = $event->getInvoker(); + + if ( ! $record->$name || + (false !== $this->_options['canUpdate'] && + array_key_exists($name, $record->getModified()))) { + $record->$name = $this->buildSlug($record); + } + } + } + + protected function buildSlug($record) + { + if (empty($this->_options['fields'])) { + if (method_exists($record, 'getUniqueSlug')) { + $value = $record->getUniqueSlug($record); + } else { + $value = (string) $record; + } + } else { + if ($this->_options['unique'] === true) { + $value = $this->getUniqueSlug($record); + } else { + $value = ''; + foreach ($this->_options['fields'] as $field) { + $value .= $record->$field . ' '; + } + } + } + + $value = call_user_func_array($this->_options['builder'], array($value, $record)); + + return $value; + } + + public function getUniqueSlug($record) + { + $name = $this->_options['name']; + $slugFromFields = ''; + foreach ($this->_options['fields'] as $field) { + $slugFromFields .= $record->$field . ' '; + } + + $proposal = $record->$name ? $record->$name : $slugFromFields; + $proposal = call_user_func_array($this->_options['builder'], array($proposal, $record)); + $slug = $proposal; + + $whereString = 'r.' . $name . ' LIKE ?'; + $whereParams = array($proposal.'%'); + + if ($record->exists()) { + $identifier = $record->identifier(); + $whereString .= ' AND r.' . implode(' != ? AND r.', $record->getTable()->getIdentifierColumnNames()) . ' != ?'; + $whereParams = array_merge($whereParams, array_values($identifier)); + } + + foreach ($this->_options['uniqueBy'] as $uniqueBy) { + if (is_null($record->$uniqueBy)) { + $whereString .= ' AND r.'.$uniqueBy.' IS NULL'; + } else { + $whereString .= ' AND r.'.$uniqueBy.' = ?'; + $whereParams[] = $record->$uniqueBy; + } + } + + $query = IPF_ORM_Query::create() + ->select('r.'.$name) + ->from(get_class($record).' r') + ->where($whereString , $whereParams) + ->setHydrationMode(IPF_ORM::HYDRATE_ARRAY); + + $similarSlugResult = $query->execute(); + + $similarSlugs = array(); + foreach ($similarSlugResult as $key => $value) { + $similarSlugs[$key] = $value[$name]; + } + + $i = 1; + while (in_array($slug, $similarSlugs)) { + $slug = $proposal.'-'.$i; + $i++; + } + + return $slug; + } +} diff --git a/src/orm/template/listener/timestampable.php b/src/orm/template/listener/timestampable.php new file mode 100644 index 0000000..67afd8c --- /dev/null +++ b/src/orm/template/listener/timestampable.php @@ -0,0 +1,48 @@ +_options = $options; + } + + public function preInsert(IPF_ORM_Event $event) + { + if( ! $this->_options['created']['disabled']) { + $createdName = $this->_options['created']['name']; + $event->getInvoker()->$createdName = $this->getTimestamp('created'); + } + + if( ! $this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) { + $updatedName = $this->_options['updated']['name']; + $event->getInvoker()->$updatedName = $this->getTimestamp('updated'); + } + } + + public function preUpdate(IPF_ORM_Event $event) + { + if( ! $this->_options['updated']['disabled']) { + $updatedName = $this->_options['updated']['name']; + $event->getInvoker()->$updatedName = $this->getTimestamp('updated'); + } + } + + public function getTimestamp($type) + { + $options = $this->_options[$type]; + + if ($options['expression'] !== false && is_string($options['expression'])) { + return new IPF_ORM_Expression($options['expression']); + } else { + if ($options['type'] == 'date') { + return gmdate($options['format']); + } else if ($options['type'] == 'timestamp') { + return gmdate($options['format']); + } else { + return gmmktime(); + } + } + } +} diff --git a/src/orm/template/orderable.php b/src/orm/template/orderable.php new file mode 100644 index 0000000..4ed0a33 --- /dev/null +++ b/src/orm/template/orderable.php @@ -0,0 +1,33 @@ +columnName = $options['name']; + if (array_key_exists('exclude', $options)) + $this->exclude = $options['exclude']; + if (array_key_exists('prepend', $options)) + $this->prepend = $options['prepend']; + } + } + + public function getColumnName() + { + return $this->columnName; + } + + public function setTableDefinition(IPF_ORM_Table $table) + { + $table->setColumn($this->columnName, 'integer', null, array('exclude' => $this->exclude)); + $table->addIndex($table->getOption('tableName') . '_orderable_' . $this->columnName, array('fields' => array($this->columnName))); + $table->listeners['Orderable_'.$this->columnName] = new IPF_ORM_Template_Listener_Orderable($this->columnName, $this->prepend); + } +} + diff --git a/src/orm/template/owned.php b/src/orm/template/owned.php new file mode 100644 index 0000000..8134201 --- /dev/null +++ b/src/orm/template/owned.php @@ -0,0 +1,49 @@ +columnName = $options['column']; + if (array_key_exists('name', $options)) + $this->name = $options['name']; + if (array_key_exists('exclude', $options)) + $this->exclude = $options['exclude']; + if (array_key_exists('verbose', $options)) + $this->verbose = $options['verbose']; + } + } + + public function getColumnName() + { + return $this->columnName; + } + + public function setTableDefinition(IPF_ORM_Table $table) + { + $table->setColumn($this->columnName, 'integer', null, array( + 'exclude' => $this->exclude, + 'verbose' => $this->verbose, + )); + + $fks = $table->getOption('foreignKeys', array()); + $fks[] = array( + 'local' => $this->columnName, + 'foreign' => 'id', + 'foreignTable' => 'auth_users', + 'onUpdate' => null, + 'onDelete' => 'CASCADE', + ); + $table->setOption('foreignKeys', $fks); + + $table->listeners['Owned_'.$this->columnName] = new IPF_ORM_Template_Listener_Owned($this->columnName); + } +} + diff --git a/src/orm/template/sluggable.php b/src/orm/template/sluggable.php new file mode 100644 index 0000000..30d14ed --- /dev/null +++ b/src/orm/template/sluggable.php @@ -0,0 +1,35 @@ + 'slug', + 'type' => 'string', + 'length' => 255, + 'unique' => true, + 'options' => array(), + 'fields' => array(), + 'uniqueBy' => array(), + 'uniqueIndex' => true, + 'canUpdate' => false, + 'builder' => array('IPF_ORM_Inflector', 'urlize'), + 'indexName' => 'sluggable' + ); + + public function __construct(array $options = array()) + { + $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options); + } + + public function setTableDefinition(IPF_ORM_Table $table) + { + $table->setColumn($this->_options['name'], $this->_options['type'], $this->_options['length'], $this->_options['options']); + + if ($this->_options['unique'] == true && $this->_options['uniqueIndex'] == true && !empty($this->_options['fields'])) { + $indexFields = array($this->_options['name']); + $indexFields = array_merge($indexFields, $this->_options['uniqueBy']); + $table->addIndex($this->_options['indexName'], array('fields' => $indexFields, 'type' => 'unique')); + } + $table->listeners['Sluggable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Sluggable($this->_options); + } +} + diff --git a/src/orm/template/timestampable.php b/src/orm/template/timestampable.php new file mode 100644 index 0000000..117b025 --- /dev/null +++ b/src/orm/template/timestampable.php @@ -0,0 +1,42 @@ + array( + 'name' => 'created_at', + 'type' => 'timestamp', + 'format' => 'Y-m-d H:i:s', + 'disabled' => false, + 'expression' => false, + 'options' => array('exclude' => true), + ), + 'updated' => array( + 'name' => 'updated_at', + 'type' => 'timestamp', + 'format' => 'Y-m-d H:i:s', + 'disabled' => false, + 'expression' => false, + 'onInsert' => true, + 'exclude' => true, + 'options' => array('exclude' => true), + ), + ); + + public function __construct(array $options = array()) + { + $this->_options = IPF_ORM_Utils::arrayDeepMerge($this->_options, $options); + } + + public function setTableDefinition(IPF_ORM_Table $table) + { + if (!$this->_options['created']['disabled']) { + $table->setColumn($this->_options['created']['name'], $this->_options['created']['type'], null, $this->_options['created']['options']); + } + if (!$this->_options['updated']['disabled']) { + $table->setColumn($this->_options['updated']['name'], $this->_options['updated']['type'], null, $this->_options['updated']['options']); + } + $table->listeners['Timestampable_'.print_r($this->_options, true)] = new IPF_ORM_Template_Listener_Timestampable($this->_options); + } +} + diff --git a/src/orm/transaction.php b/src/orm/transaction.php new file mode 100644 index 0000000..cc60631 --- /dev/null +++ b/src/orm/transaction.php @@ -0,0 +1,267 @@ +_collections[] = $coll; + + return $this; + } + + public function getState() + { + switch ($this->_nestingLevel) { + case 0: + return IPF_ORM_Transaction::STATE_SLEEP; + break; + case 1: + return IPF_ORM_Transaction::STATE_ACTIVE; + break; + default: + return IPF_ORM_Transaction::STATE_BUSY; + } + } + + public function addInvalid(IPF_ORM_Record $record) + { + if (in_array($record, $this->invalid, true)) { + return false; + } + $this->invalid[] = $record; + return true; + } + + public function getInvalid() + { + return $this->invalid; + } + + public function getTransactionLevel() + { + return $this->_nestingLevel; + } + + public function getInternalTransactionLevel() + { + return $this->_internalNestingLevel; + } + + public function beginTransaction($savepoint = null) + { + if ( ! is_null($savepoint)) { + $this->savePoints[] = $savepoint; + + $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_CREATE); + + $this->conn->notifyDBListeners('preSavepointCreate', $event); + + if ( ! $event->skipOperation) { + $this->createSavePoint($savepoint); + } + + $this->conn->notifyDBListeners('postSavepointCreate', $event); + } else { + if ($this->_nestingLevel == 0) { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_BEGIN); + + $this->conn->notifyDBListeners('preTransactionBegin', $event); + + if ( ! $event->skipOperation) { + try { + $this->_doBeginTransaction(); + } catch (Exception $e) { + throw new IPF_ORM_Exception($e->getMessage()); + } + } + $this->conn->notifyDBListeners('postTransactionBegin', $event); + } + } + + $level = ++$this->_nestingLevel; + + return $level; + } + + public function commit($savepoint = null) + { + if ($this->_nestingLevel == 0) { + throw new IPF_ORM_Exception("Commit failed. There is no active transaction."); + } + + if ( ! is_null($savepoint)) { + $this->_nestingLevel -= $this->removeSavePoints($savepoint); + + $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_COMMIT); + + $this->conn->notifyDBListeners('preSavepointCommit', $event); + + if ( ! $event->skipOperation) { + $this->releaseSavePoint($savepoint); + } + + $this->conn->notifyDBListeners('postSavepointCommit', $event); + } else { + + if ($this->_nestingLevel == 1 || $this->_internalNestingLevel == 1) { + if ( ! empty($this->invalid)) { + if ($this->_internalNestingLevel == 1) { + // transaction was started by IPF_ORM, so we are responsible + // for a rollback + $this->rollback(); + $tmp = $this->invalid; + $this->invalid = array(); + throw new IPF_ORM_Exception_Validator($tmp); + } + } + if ($this->_nestingLevel == 1) { + // take snapshots of all collections used within this transaction + foreach ($this->_collections as $coll) { + $coll->takeSnapshot(); + } + $this->_collections = array(); + + $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_COMMIT); + + $this->conn->notifyDBListeners('preTransactionCommit', $event); + if ( ! $event->skipOperation) { + $this->_doCommit(); + } + $this->conn->notifyDBListeners('postTransactionCommit', $event); + } + } + + if ($this->_nestingLevel > 0) { + $this->_nestingLevel--; + } + if ($this->_internalNestingLevel > 0) { + $this->_internalNestingLevel--; + } + } + + return true; + } + + public function rollback($savepoint = null) + { + if ($this->_nestingLevel == 0) { + throw new IPF_ORM_Exception("Rollback failed. There is no active transaction."); + } + + if ($this->_internalNestingLevel > 1 || $this->_nestingLevel > 1) { + $this->_internalNestingLevel--; + $this->_nestingLevel--; + return false; + } + + if ( ! is_null($savepoint)) { + $this->_nestingLevel -= $this->removeSavePoints($savepoint); + + $event = new IPF_ORM_Event($this, IPF_ORM_Event::SAVEPOINT_ROLLBACK); + + $this->conn->notifyDBListeners('preSavepointRollback', $event); + + if ( ! $event->skipOperation) { + $this->rollbackSavePoint($savepoint); + } + + $this->conn->notifyDBListeners('postSavepointRollback', $event); + } else { + $event = new IPF_ORM_Event($this, IPF_ORM_Event::TX_ROLLBACK); + + $this->conn->notifyDBListeners('preTransactionRollback', $event); + + if ( ! $event->skipOperation) { + $this->_nestingLevel = 0; + $this->_internalNestingLevel = 0; + try { + $this->_doRollback(); + } catch (Exception $e) { + throw new IPF_ORM_Exception($e->getMessage()); + } + } + + $this->conn->notifyDBListeners('postTransactionRollback', $event); + } + + return true; + } + + protected function createSavePoint($savepoint) + { + throw new IPF_ORM_Exception('Savepoints not supported by this driver.'); + } + + protected function releaseSavePoint($savepoint) + { + throw new IPF_ORM_Exception('Savepoints not supported by this driver.'); + } + + protected function rollbackSavePoint($savepoint) + { + throw new IPF_ORM_Exception('Savepoints not supported by this driver.'); + } + + protected function _doRollback() + { + $this->conn->getDbh()->rollback(); + } + + protected function _doCommit() + { + $this->conn->getDbh()->commit(); + } + + protected function _doBeginTransaction() + { + $this->conn->getDbh()->beginTransaction(); + } + + private function removeSavePoints($savepoint) + { + $this->savePoints = array_values($this->savePoints); + + $found = false; + $i = 0; + + foreach ($this->savePoints as $key => $sp) { + if ( ! $found) { + if ($sp === $savepoint) { + $found = true; + } + } + if ($found) { + $i++; + unset($this->savePoints[$key]); + } + } + + return $i; + } + + public function setIsolation($isolation) + { + throw new IPF_ORM_Exception('Transaction isolation levels not supported by this driver.'); + } + + public function getIsolation() + { + throw new IPF_ORM_Exception('Fetching transaction isolation level not supported by this driver.'); + } + + public function beginInternalTransaction($savepoint = null) + { + $this->_internalNestingLevel++; + return $this->beginTransaction($savepoint); + } +} diff --git a/src/orm/transaction/mysql.php b/src/orm/transaction/mysql.php new file mode 100644 index 0000000..20597ff --- /dev/null +++ b/src/orm/transaction/mysql.php @@ -0,0 +1,47 @@ +conn->execute($query); + } + + protected function releaseSavePoint($savepoint) + { + $query = 'RELEASE SAVEPOINT ' . $savepoint; + + return $this->conn->execute($query); + } + + protected function rollbackSavePoint($savepoint) + { + $query = 'ROLLBACK TO SAVEPOINT ' . $savepoint; + + return $this->conn->execute($query); + } + + public function setIsolation($isolation) + { + switch ($isolation) { + case 'READ UNCOMMITTED': + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + break; + default: + throw new IPF_ORM_Exception('Isolation level ' . $isolation . ' is not supported.'); + } + + $query = 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $isolation; + + return $this->conn->execute($query); + } + + public function getIsolation() + { + return $this->conn->fetchOne('SELECT @@tx_isolation'); + } +} \ No newline at end of file diff --git a/src/orm/utils.php b/src/orm/utils.php new file mode 100644 index 0000000..e6b55f2 --- /dev/null +++ b/src/orm/utils.php @@ -0,0 +1,129 @@ +'; + $r[] = 'IPF_ORM_Connection object'; + $r[] = 'State : ' . IPF_ORM_Utils::getConnectionStateAsString($connection->transaction->getState()); + $r[] = 'Open Transactions : ' . $connection->transaction->getTransactionLevel(); + $r[] = 'Table in memory : ' . $connection->count(); + $r[] = 'Driver name : ' . $connection->getDriverName(); + $r[] = ""; + return implode("\n",$r)."
"; + } + + public static function getValidators() + { + return array( + 'country', + 'creditcard', + 'date', + 'driver', + 'email', + 'exception', + 'future', + 'htmlcolor', + 'ip', + 'minlength', + 'nospace', + 'notblank', + 'notnull', + 'past', + 'range', + 'readonly', + 'regexp', + 'time', + 'timestamp', + 'unique', + 'unsigned', + 'usstate', + 'file', + 'image', + 'html', + 'uploadTo', + 'verbose', + ); + } + + + public static function arrayDeepMerge() + { + switch (func_num_args()) { + case 0: + return false; + case 1: + return func_get_arg(0); + case 2: + $args = func_get_args(); + $args[2] = array(); + + if (is_array($args[0]) && is_array($args[1])) + { + foreach (array_unique(array_merge(array_keys($args[0]),array_keys($args[1]))) as $key) + { + $isKey0 = array_key_exists($key, $args[0]); + $isKey1 = array_key_exists($key, $args[1]); + + if ($isKey0 && $isKey1 && is_array($args[0][$key]) && is_array($args[1][$key])) + { + $args[2][$key] = self::arrayDeepMerge($args[0][$key], $args[1][$key]); + } else if ($isKey0 && $isKey1) { + $args[2][$key] = $args[1][$key]; + } else if ( ! $isKey1) { + $args[2][$key] = $args[0][$key]; + } else if ( ! $isKey0) { + $args[2][$key] = $args[1][$key]; + } + } + + return $args[2]; + } else { + return $args[1]; + } + default: + $args = func_get_args(); + $args[1] = self::arrayDeepMerge($args[0], $args[1]); + array_shift($args); + return call_user_func_array(array('IPF', 'arrayDeepMerge'), $args); + break; + } + } + + public static function getTableAsString(IPF_ORM_Table $table) + { + $r[] = "
";
+        $r[] = "Component   : ".$table->getComponentName();
+        $r[] = "Table       : ".$table->getTableName();
+        $r[] = "
"; + + return implode("\n",$r)."
"; + } + + public static function getCollectionAsString(IPF_ORM_Collection $collection) + { + $r[] = "
";
+        $r[] = get_class($collection);
+        $r[] = 'data : ' . IPF_ORM::dump($collection->getData(), false);
+        //$r[] = 'snapshot : ' . IPF_ORM::dump($collection->getSnapshot());
+        $r[] = "
"; + return implode("\n",$r); + } +} + diff --git a/src/orm/validator.php b/src/orm/validator.php new file mode 100644 index 0000000..104d089 --- /dev/null +++ b/src/orm/validator.php @@ -0,0 +1,149 @@ +getTable(); + + // if record is transient all fields will be validated + // if record is persistent only the modified fields will be validated + $fields = $record->exists() ? $record->getModified():$record->getData(); + foreach ($fields as $fieldName => $value) { + $this->validateField($table, $fieldName, $value, $record); + } + } + + private function validateField(IPF_ORM_Table $table, $fieldName, $value, IPF_ORM_Record $record) + { + if (IPF_ORM_Null::isNull($value)) { + $value = null; + } else if ($value instanceof IPF_ORM_Record) { + $value = $value->getIncremented(); + } + + $dataType = $table->getTypeOf($fieldName); + + // Validate field type + if (!IPF_ORM_Validator::isValidType($value, $dataType)) { + $record->addError($fieldName, 'type'); + } + + if ($dataType == 'enum') { + $enumIndex = $table->enumIndex($fieldName, $value); + if ($enumIndex === false) { + $record->addError($fieldName, 'enum'); + } + } + + // Validate field length + $definition = $table->getDefinitionOf($fieldName); + if (!$this->validateLength($value, $dataType, $definition['length'])) { + $record->addError($fieldName, 'length'); + } + + // Run all custom validators + foreach ($table->getFieldValidators($fieldName) as $validatorName => $args) { + if (!is_string($validatorName)) { + $validatorName = $args; + $args = array(); + } + + $validator = IPF_ORM_Validator::getValidator($validatorName); + $validator->invoker = $record; + $validator->field = $fieldName; + $validator->args = $args; + if (!$validator->validate($value)) { + $record->addError($fieldName, $validatorName); + } + } + } + + public function validateLength($value, $type, $maximumLength) + { + if ($type == 'timestamp' || $type == 'integer' || $type == 'enum') { + return true; + } else if ($type == 'array' || $type == 'object') { + $length = strlen(serialize($value)); + } else if ($type == 'decimal') { + if (!$maximumLength) + $maximumLength = 18; + $length = strlen($value); + } else { + $length = strlen($value); + } + + if ($length > $maximumLength) { + return false; + } + return true; + } + + public function hasErrors() + { + return (count($this->stack) > 0); + } + + public static function isValidType($var, $type) + { + if ($var instanceof IPF_ORM_Expression) { + return true; + } else if ($var === null) { + return true; + } else if (is_object($var)) { + return $type == 'object'; + } + + switch ($type) { + case 'float': + case 'double': + case 'decimal': + return (string)$var == strval(floatval($var)); + case 'integer': + return (string)$var == strval(intval($var)); + case 'string': + return is_string($var) || is_numeric($var); + case 'blob': + case 'clob': + case 'gzip': + return is_string($var); + case 'array': + return is_array($var); + case 'object': + return is_object($var); + case 'boolean': + return is_bool($var) || (is_numeric($var) && ($var == 0 || $var == 1)); + case 'timestamp': + $validator = self::getValidator('timestamp'); + return $validator->validate($var); + case 'time': + $validator = self::getValidator('time'); + return $validator->validate($var); + case 'date': + $validator = self::getValidator('date'); + return $validator->validate($var); + case 'enum': + return is_string($var) || is_int($var); + default: + return false; + } + } +} + diff --git a/src/orm/validator/country.php b/src/orm/validator/country.php new file mode 100644 index 0000000..966ede8 --- /dev/null +++ b/src/orm/validator/country.php @@ -0,0 +1,261 @@ + 'Andorra', + 'ae' => 'United Arab Emirates', + 'af' => 'Afghanistan', + 'ag' => 'Antigua and Barbuda', + 'ai' => 'Anguilla', + 'al' => 'Albania', + 'am' => 'Armenia', + 'an' => 'Netherlands Antilles', + 'ao' => 'Angola', + 'aq' => 'Antarctica', + 'ar' => 'Argentina', + 'as' => 'American Samoa', + 'at' => 'Austria', + 'au' => 'Australia', + 'aw' => 'Aruba', + 'az' => 'Azerbaijan', + 'ba' => 'Bosnia Hercegovina', + 'bb' => 'Barbados', + 'bd' => 'Bangladesh', + 'be' => 'Belgium', + 'bf' => 'Burkina Faso', + 'bg' => 'Bulgaria', + 'bh' => 'Bahrain', + 'bi' => 'Burundi', + 'bj' => 'Benin', + 'bm' => 'Bermuda', + 'bn' => 'Brunei Darussalam', + 'bo' => 'Bolivia', + 'br' => 'Brazil', + 'bs' => 'Bahamas', + 'bt' => 'Bhutan', + 'bv' => 'Bouvet Island', + 'bw' => 'Botswana', + 'by' => 'Belarus (Byelorussia)', + 'bz' => 'Belize', + 'ca' => 'Canada', + 'cc' => 'Cocos Islands', + 'cd' => 'Congo, The Democratic Republic of the', + 'cf' => 'Central African Republic', + 'cg' => 'Congo', + 'ch' => 'Switzerland', + 'ci' => 'Ivory Coast', + 'ck' => 'Cook Islands', + 'cl' => 'Chile', + 'cm' => 'Cameroon', + 'cn' => 'China', + 'co' => 'Colombia', + 'cr' => 'Costa Rica', + 'cs' => 'Czechoslovakia', + 'cu' => 'Cuba', + 'cv' => 'Cape Verde', + 'cx' => 'Christmas Island', + 'cy' => 'Cyprus', + 'cz' => 'Czech Republic', + 'de' => 'Germany', + 'dj' => 'Djibouti', + 'dk' => 'Denmark', + 'dm' => 'Dominica', + 'do' => 'Dominican Republic', + 'dz' => 'Algeria', + 'ec' => 'Ecuador', + 'ee' => 'Estonia', + 'eg' => 'Egypt', + 'eh' => 'Western Sahara', + 'er' => 'Eritrea', + 'es' => 'Spain', + 'et' => 'Ethiopia', + 'fi' => 'Finland', + 'fj' => 'Fiji', + 'fk' => 'Falkland Islands', + 'fm' => 'Micronesia', + 'fo' => 'Faroe Islands', + 'fr' => 'France', + 'fx' => 'France, Metropolitan FX', + 'ga' => 'Gabon', + 'gb' => 'United Kingdom (Great Britain)', + 'gd' => 'Grenada', + 'ge' => 'Georgia', + 'gf' => 'French Guiana', + 'gh' => 'Ghana', + 'gi' => 'Gibraltar', + 'gl' => 'Greenland', + 'gm' => 'Gambia', + 'gn' => 'Guinea', + 'gp' => 'Guadeloupe', + 'gq' => 'Equatorial Guinea', + 'gr' => 'Greece', + 'gs' => 'South Georgia and the South Sandwich Islands', + 'gt' => 'Guatemala', + 'gu' => 'Guam', + 'gw' => 'Guinea-bissau', + 'gy' => 'Guyana', + 'hk' => 'Hong Kong', + 'hm' => 'Heard and McDonald Islands', + 'hn' => 'Honduras', + 'hr' => 'Croatia', + 'ht' => 'Haiti', + 'hu' => 'Hungary', + 'id' => 'Indonesia', + 'ie' => 'Ireland', + 'il' => 'Israel', + 'in' => 'India', + 'io' => 'British Indian Ocean Territory', + 'iq' => 'Iraq', + 'ir' => 'Iran', + 'is' => 'Iceland', + 'it' => 'Italy', + 'jm' => 'Jamaica', + 'jo' => 'Jordan', + 'jp' => 'Japan', + 'ke' => 'Kenya', + 'kg' => 'Kyrgyzstan', + 'kh' => 'Cambodia', + 'ki' => 'Kiribati', + 'km' => 'Comoros', + 'kn' => 'Saint Kitts and Nevis', + 'kp' => 'North Korea', + 'kr' => 'South Korea', + 'kw' => 'Kuwait', + 'ky' => 'Cayman Islands', + 'kz' => 'Kazakhstan', + 'la' => 'Laos', + 'lb' => 'Lebanon', + 'lc' => 'Saint Lucia', + 'li' => 'Lichtenstein', + 'lk' => 'Sri Lanka', + 'lr' => 'Liberia', + 'ls' => 'Lesotho', + 'lt' => 'Lithuania', + 'lu' => 'Luxembourg', + 'lv' => 'Latvia', + 'ly' => 'Libya', + 'ma' => 'Morocco', + 'mc' => 'Monaco', + 'md' => 'Moldova Republic', + 'mg' => 'Madagascar', + 'mh' => 'Marshall Islands', + 'mk' => 'Macedonia, The Former Yugoslav Republic of', + 'ml' => 'Mali', + 'mm' => 'Myanmar', + 'mn' => 'Mongolia', + 'mo' => 'Macau', + 'mp' => 'Northern Mariana Islands', + 'mq' => 'Martinique', + 'mr' => 'Mauritania', + 'ms' => 'Montserrat', + 'mt' => 'Malta', + 'mu' => 'Mauritius', + 'mv' => 'Maldives', + 'mw' => 'Malawi', + 'mx' => 'Mexico', + 'my' => 'Malaysia', + 'mz' => 'Mozambique', + 'na' => 'Namibia', + 'nc' => 'New Caledonia', + 'ne' => 'Niger', + 'nf' => 'Norfolk Island', + 'ng' => 'Nigeria', + 'ni' => 'Nicaragua', + 'nl' => 'Netherlands', + 'no' => 'Norway', + 'np' => 'Nepal', + 'nr' => 'Nauru', + 'nt' => 'Neutral Zone', + 'nu' => 'Niue', + 'nz' => 'New Zealand', + 'om' => 'Oman', + 'pa' => 'Panama', + 'pe' => 'Peru', + 'pf' => 'French Polynesia', + 'pg' => 'Papua New Guinea', + 'ph' => 'Philippines', + 'pk' => 'Pakistan', + 'pl' => 'Poland', + 'pm' => 'St. Pierre and Miquelon', + 'pn' => 'Pitcairn', + 'pr' => 'Puerto Rico', + 'pt' => 'Portugal', + 'pw' => 'Palau', + 'py' => 'Paraguay', + 'qa' => 'Qatar', + 're' => 'Reunion', + 'ro' => 'Romania', + 'ru' => 'Russia', + 'rw' => 'Rwanda', + 'sa' => 'Saudi Arabia', + 'sb' => 'Solomon Islands', + 'sc' => 'Seychelles', + 'sd' => 'Sudan', + 'se' => 'Sweden', + 'sg' => 'Singapore', + 'sh' => 'St. Helena', + 'si' => 'Slovenia', + 'sj' => 'Svalbard and Jan Mayen Islands', + 'sk' => 'Slovakia (Slovak Republic)', + 'sl' => 'Sierra Leone', + 'sm' => 'San Marino', + 'sn' => 'Senegal', + 'so' => 'Somalia', + 'sr' => 'Suriname', + 'st' => 'Sao Tome and Principe', + 'sv' => 'El Salvador', + 'sy' => 'Syria', + 'sz' => 'Swaziland', + 'tc' => 'Turks and Caicos Islands', + 'td' => 'Chad', + 'tf' => 'French Southern Territories', + 'tg' => 'Togo', + 'th' => 'Thailand', + 'tj' => 'Tajikistan', + 'tk' => 'Tokelau', + 'tm' => 'Turkmenistan', + 'tn' => 'Tunisia', + 'to' => 'Tonga', + 'tp' => 'East Timor', + 'tr' => 'Turkey', + 'tt' => 'Trinidad, Tobago', + 'tv' => 'Tuvalu', + 'tw' => 'Taiwan', + 'tz' => 'Tanzania', + 'ua' => 'Ukraine', + 'ug' => 'Uganda', + 'uk' => 'United Kingdom', + 'um' => 'United States Minor Islands', + 'us' => 'United States of America', + 'uy' => 'Uruguay', + 'uz' => 'Uzbekistan', + 'va' => 'Vatican City', + 'vc' => 'Saint Vincent, Grenadines', + 've' => 'Venezuela', + 'vg' => 'Virgin Islands (British)', + 'vi' => 'Virgin Islands (USA)', + 'vn' => 'Viet Nam', + 'vu' => 'Vanuatu', + 'wf' => 'Wallis and Futuna Islands', + 'ws' => 'Samoa', + 'ye' => 'Yemen', + 'yt' => 'Mayotte', + 'yu' => 'Yugoslavia', + 'za' => 'South Africa', + 'zm' => 'Zambia', + 'zr' => 'Zaire', + 'zw' => 'Zimbabwe'); + + public static function getCountries() + { + return self::$countries; + } + + public function validate($value) + { + $value = strtolower($value); + return isset(self::$countries[$value]); + } +} + diff --git a/src/orm/validator/creditcard.php b/src/orm/validator/creditcard.php new file mode 100644 index 0000000..62609ac --- /dev/null +++ b/src/orm/validator/creditcard.php @@ -0,0 +1,43 @@ + 'visa', + "/^5[12345]\d{14}$/" => 'mastercard', + "/^3[47]\d{13}$/" => 'amex', + "/^6011\d{12}$/" => 'discover', + "/^30[012345]\d{11}$/" => 'diners', + "/^3[68]\d{12}$/" => 'diners', + ); + + $cardType = ''; + foreach ($card_regexes as $regex => $type) { + if (preg_match($regex, $value)) { + $cardType = $type; + break; + } + } + if (!$cardType) + return false; + + /* mod 10 checksum algorithm */ + $revcode = strrev($value); + $checksum = 0; + for ($i = 0; $i < strlen($revcode); $i++) { + $currentNum = intval($revcode[$i]); + if ($i & 1) { /* Odd position */ + $currentNum *= 2; + } + /* Split digits and add. */ + $checksum += $currentNum % 10; + if ($currentNum > 9) { + $checksum += 1; + } + } + return $checksum % 10 == 0; + } +} + diff --git a/src/orm/validator/date.php b/src/orm/validator/date.php new file mode 100644 index 0000000..b0916a9 --- /dev/null +++ b/src/orm/validator/date.php @@ -0,0 +1,17 @@ +_args[$arg])) { + return $this->_args[$arg]; + } + return null; + } + + public function __isset($arg) + { + return isset($this->_args[$arg]); + } + + public function __set($arg, $value) + { + $this->_args[$arg] = $value; + + return $this; + } + + public function getArg($arg) + { + if ( ! isset($this->_args[$arg])) { + throw new IPF_ORM_Exception_Validator('Unknown option ' . $arg); + } + return $this->_args[$arg]; + } + + public function setArg($arg, $value) + { + $this->_args[$arg] = $value; + + return $this; + } + + public function getArgs() + { + return $this->_args; + } +} \ No newline at end of file diff --git a/src/orm/validator/email.php b/src/orm/validator/email.php new file mode 100644 index 0000000..6e07505 --- /dev/null +++ b/src/orm/validator/email.php @@ -0,0 +1,11 @@ +args) && isset($this->args['timezone'])) { + switch (strtolower($this->args['timezone'])) { + case 'gmt': + $now = gmdate("U") - date("Z"); + break; + default: + $now = getdate(); + break; + } + } else { + $now = getdate(); + } + + if ($now['year'] > $e[0]) { + return false; + } else if ($now['year'] == $e[0]) { + if ($now['mon'] > $e[1]) { + return false; + } else if ($now['mon'] == $e[1]) { + return $now['mday'] < $e[2]; + } else { + return true; + } + } else { + return true; + } + } +} \ No newline at end of file diff --git a/src/orm/validator/html.php b/src/orm/validator/html.php new file mode 100644 index 0000000..bd663d0 --- /dev/null +++ b/src/orm/validator/html.php @@ -0,0 +1,8 @@ +args) && strlen($value) < $this->args) { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/orm/validator/nospace.php b/src/orm/validator/nospace.php new file mode 100644 index 0000000..b9f326c --- /dev/null +++ b/src/orm/validator/nospace.php @@ -0,0 +1,9 @@ +args) && isset($this->args['timezone'])) { + switch (strtolower($this->args['timezone'])) { + case 'gmt': + $now = gmdate("U") - date("Z"); + break; + default: + $now = getdate(); + break; + } + } else { + $now = getdate(); + } + + if ($now['year'] < $e[0]) { + return false; + } else if ($now['year'] == $e[0]) { + if ($now['mon'] < $e[1]) { + return false; + } else if ($now['mon'] == $e[1]) { + return $now['mday'] > $e[2]; + } else { + return true; + } + } else { + return true; + } + } +} \ No newline at end of file diff --git a/src/orm/validator/range.php b/src/orm/validator/range.php new file mode 100644 index 0000000..2a895d3 --- /dev/null +++ b/src/orm/validator/range.php @@ -0,0 +1,15 @@ +args[0]) && $value < $this->args[0]) { + return false; + } + if (isset($this->args[1]) && $value > $this->args[1]) { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/orm/validator/readonly.php b/src/orm/validator/readonly.php new file mode 100644 index 0000000..dac58f8 --- /dev/null +++ b/src/orm/validator/readonly.php @@ -0,0 +1,10 @@ +invoker->getModified(); + return array_key_exists($this->field, $modified) ? false : true; + } +} diff --git a/src/orm/validator/regexp.php b/src/orm/validator/regexp.php new file mode 100644 index 0000000..3981f13 --- /dev/null +++ b/src/orm/validator/regexp.php @@ -0,0 +1,25 @@ +args)) { + return true; + } + if (is_array($this->args)) { + foreach ($this->args as $regexp) { + if ( ! preg_match($regexp, $value)) { + return false; + } + } + return true; + } else { + if (preg_match($this->args, $value)) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/orm/validator/time.php b/src/orm/validator/time.php new file mode 100644 index 0000000..7c90fd4 --- /dev/null +++ b/src/orm/validator/time.php @@ -0,0 +1,27 @@ += 0 && $hr <= 23 && $min >= 0 && $min <= 59 && $sec >= 0 && $sec <= 59; + } +} \ No newline at end of file diff --git a/src/orm/validator/timestamp.php b/src/orm/validator/timestamp.php new file mode 100644 index 0000000..ab95161 --- /dev/null +++ b/src/orm/validator/timestamp.php @@ -0,0 +1,30 @@ +validate($date)) { + return false; + } + + if ( ! $timeValidator->validate($time)) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/src/orm/validator/unique.php b/src/orm/validator/unique.php new file mode 100644 index 0000000..8f03404 --- /dev/null +++ b/src/orm/validator/unique.php @@ -0,0 +1,35 @@ +invoker->getTable(); + $pks = $table->getIdentifier(); + + if ( is_array($pks) ) { + $pks = join(',', $pks); + } + + $sql = 'SELECT ' . $pks . ' FROM ' . $table->getTableName() . ' WHERE ' . $this->field . ' = ?'; + + $values = array(); + $values[] = $value; + + // If the record is not new we need to add primary key checks because its ok if the + // unique value already exists in the database IF the record in the database is the same + // as the one that is validated here. + $state = $this->invoker->state(); + if ( ! ($state == IPF_ORM_Record::STATE_TDIRTY || $state == IPF_ORM_Record::STATE_TCLEAN)) { + foreach ((array) $table->getIdentifier() as $pk) { + $sql .= " AND {$pk} != ?"; + $values[] = $this->invoker->$pk; + } + } + + $stmt = $table->getConnection()->getDbh()->prepare($sql); + $stmt->execute($values); + + return ( ! is_array($stmt->fetch())); + } +} \ No newline at end of file diff --git a/src/orm/validator/unsigned.php b/src/orm/validator/unsigned.php new file mode 100644 index 0000000..7f6cf1e --- /dev/null +++ b/src/orm/validator/unsigned.php @@ -0,0 +1,14 @@ + true, + 'AL' => true, + 'AR' => true, + 'AZ' => true, + 'CA' => true, + 'CO' => true, + 'CT' => true, + 'DC' => true, + 'DE' => true, + 'FL' => true, + 'GA' => true, + 'HI' => true, + 'IA' => true, + 'ID' => true, + 'IL' => true, + 'IN' => true, + 'KS' => true, + 'KY' => true, + 'LA' => true, + 'MA' => true, + 'MD' => true, + 'ME' => true, + 'MI' => true, + 'MN' => true, + 'MO' => true, + 'MS' => true, + 'MT' => true, + 'NC' => true, + 'ND' => true, + 'NE' => true, + 'NH' => true, + 'NJ' => true, + 'NM' => true, + 'NV' => true, + 'NY' => true, + 'OH' => true, + 'OK' => true, + 'OR' => true, + 'PA' => true, + 'PR' => true, + 'RI' => true, + 'SC' => true, + 'SD' => true, + 'TN' => true, + 'TX' => true, + 'UT' => true, + 'VA' => true, + 'VI' => true, + 'VT' => true, + 'WA' => true, + 'WI' => true, + 'WV' => true, + 'WY' => true + ); + public function getStates() + { + return self::$states; + } + + public function validate($value) + { + return isset(self::$states[$value]); + } +} \ No newline at end of file diff --git a/src/orm/validator/verbose.php b/src/orm/validator/verbose.php new file mode 100644 index 0000000..0a6478e --- /dev/null +++ b/src/orm/validator/verbose.php @@ -0,0 +1,10 @@ +