Троичная логика (ternary logic) в Doctrine ORM
В этой статье речь пойдет о третичной логике. Я не буду говорить об операциях в троичной логике, а коснусь лишь вопросов хранения данных в Doctrine.
Представим, что есть некоторый столбец в БД, данные которого могут принимать три возможных значения. Для хранения типа boolean (двоичная логика) Doctrine используется тип данных boolean в PHP, а при отражении в таблицу он проектируется в tinyint(1). Но как тогда кодировать третье значение? Мы будем использовать NULL. Т.е. область определения = {0,1,NULL}. Значит notnull => false. Но тогда возникает одна проблема.
1 2 3 |
$model = new SomeModel(); $model->ternary_field = NULL; $model->save(); |
При этом срабатывает приведение типов, и в БД сохраняется 0, а не NULL. Приведение типов столбцов закодировано в файле Doctrine_Record::getPrepared():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public function getPrepared(array $array = array()) { $a = array(); if (empty($array)) { $modifiedFields = $this->_modified; } foreach ($modifiedFields as $field) { $type = $this->_table->getTypeOf($field); if ($this->_data[$field] === self::$_null) { $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 'set': if (is_array($this->_data[$field])) { $a[$field] = implode(',', $this->_data[$field]); } else { $a[$field] = $this->_data[$field]; } break; default: if ($this->_data[$field] instanceof Doctrine_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 Doctrine_Record_Exception('Unexpected null value.'); } */ } } return $a; } |
А для типа boolean срабатывает вот этот case:
1 2 3 |
case 'boolean': $a[$field] = $this->getTable()->getConnection()->convertBooleans($this->_data[$field]); break; |
Определение столбца выглядело следующим образом:
1 2 3 4 |
$this->hasColumn('ternary_field', 'boolean', null, array( 'default' => false )); |
Не буду томить с решением проблемы, во общем всё портил параметр default. После его изменения на default=>NULL в базу стали сохраняться правильные значения.
Литература
http://stackoverflow.com/questions/1391777/how-do-i-insert-null-values-using-pdo
http://php.net/manual/en/pdostatement.bindparam.php
по идее нужно использовать Expression, в первой ветке делалось так.
$model->ternary_field = new Doctrine_Expression(‘NULL’);
Пробовали все методы (см. http://stackoverflow.com/questions/1391777/how-do-i-insert-null-values-using-pdo). Они все работают только в случае default => NULL, при default => false ни один не работает. А т.к. потом всё заработало, то решили остановиться на простом присваивании.