Ответ 1
Мне не нравится это решение, но добавление следующего до сохранения объекта действительно работает.
$this->Agencies->deleteAll('1=1');
Я использую Postgres (что, я думаю, связано с проблемой), и CakePHP 3.
У меня есть следующий unit test, чтобы просто проверить, может ли этот набор данных быть сохранен моделью. Когда я запускаю следующий тест, со стандартной "bake'd" Model unit test, я получаю ошибку ниже.
Я думаю, что это проблема:
Мы используем приборы для добавления некоторых базовых данных. Это единственное место, которое, я думаю, может вызвать проблемы. Чтобы добавить к этому достоверность, во время выполнения модульных тестов я выполнил следующую команду, чтобы получить следующее значение auto-incrementing id
, и оно вернулось 1, даже если оно вернуло правильный номер в не-тестовой БД. Select nextval(pg_get_serial_sequence('agencies', 'id')) as new_id;
Unit Test:
public function testValidationDefault()
{
$agencyData = [
'full_name' => 'Agency Full Name',
'mode' => 'transit',
'request_api_class' => 'Rest\Get\Json',
'response_api_class' => 'NextBus\Generic',
'realtime_url_pattern' => 'http://api.example.com',
'routes' => '{"123": {"full_route": "123 Full Route", "route_color": "#123456"}}'
];
$agency = $this->Agencies->newEntity($agencyData);
$saved = $this->Agencies->save($agency);
$this->assertInstanceOf('App\Model\Entity\Agency', $saved);
}
Ошибка:
PDOException: SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "agencies_pkey"
DETAIL: Key (id)=(1) already exists.
Что я пробовал
У этого прибора есть поле ID, задающее каждую запись. Удаление их из прибора работает, но оно прерывает другие модульные тесты, которые полагаются на некоторые реляционные данные.
Мне не нравится это решение, но добавление следующего до сохранения объекта действительно работает.
$this->Agencies->deleteAll('1=1');
[UPDATE: Мой другой ответ - это реальное решение этой проблемы! Вам больше не нужно это делать...]
Ниже приведено менее грязное обходное решение, которое не требует удаления всех записей:
use Cake\Datasource\ConnectionManager;
...
$connection = ConnectionManager::get('test');
$results = $connection->execute('ALTER SEQUENCE <tablename>_id_seq RESTART WITH 999999');
//TEST WHICH INSERTS RECORD(s)...
Похоже, что автоинкремент не устанавливается правильно / reset во время setUp()
или tearDown()
... поэтому вручную, устанавливая его на что-то действительно высокое (большее, чем количество существующих записей), предотвращает ошибка "дублировать ключ...".
Преимущество этого взлома (более deleteAll('1=1')
) заключается в том, что вы можете впоследствии запускать тесты, которые ссылаются на существующие данные БД.
Это может быть проблемой в определении вашего прибора. Cake PHP documentation использует поле _constraints
, указывающее, что поле id
является первичным ключом:
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id']],
]
Я полагаю, что, наконец, я решил РЕАЛЬНОЕ решение этой проблемы!
Я считаю, что эта проблема связана с настройкой прибора по умолчанию, которая возникает из-за использования команды bake
для создания приборов.
Когда вы bake
модель, он создает шаблон для этого светильника. Обратите внимание на autoIncrement
для свойства ID в приведенном ниже коде? Вопреки тому, что вы могли бы подумать, это должно быть не true
. Когда я устанавливаю его на null
и удаляю id
из элементов массива $records
, я больше не получаю ошибки уникальности.
public $fields = [
'id' => ['type' => 'integer', 'length' => 10, 'autoIncrement' => true, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null],
'nickname' => ['type' => 'text', 'length' => null, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null],
...
public $records = [
[
// 'id' => 1,
'nickname' => 'Foo bar',
'width' => 800,
...
Мастера ниндзя в проекте CakePHP - это герои: источник билет CakePHP
Если поля id
удаляются из записей фиксации, они будут использовать автоматическое приращение при вставке, оставляя последовательность идентификаторов таблицы в нужном месте для вставок, которые происходят во время тестов. Я считаю, что именно поэтому он работает для @emersonthis, как описано выше.
У этого решения есть еще одна проблема: вы не можете создавать надежные отношения между записями приборов, потому что вы не знаете, какие идентификаторы они получат. Что вы помещаете в поле внешнего идентификатора связанной таблицы? Это привело меня к первоначальному решению просто изменить последовательность таблиц после того, как были вставлены записи с жестко закодированными идентификаторами. Сейчас я делаю это в затронутых TestCases:
public $fixtures = [
'app.articles',
'app.authors',
];
...
public function setUp()
{
$connection = \Cake\Datasource\ConnectionManager::get('test');
foreach ($this->fixtures as $fixture) {
$tableName = explode('.', $fixture)[1];
$connection->execute("
SELECT setval(
pg_get_serial_sequence('$tableName', 'id'),
(SELECT MAX(id) FROM $tableName)
)");
}
}
Это приводит к тому, что последовательность автоматического увеличения увеличивается до самого высокого ранее использованного идентификатора. В следующий раз, когда идентификатор генерируется из последовательности, он будет один выше, разрешая проблему во всех случаях.
Включение одного из этих решений в предстоящий выпуск CakePHP обсуждается здесь.