SQL Миграция через Phinx. Ошибка при миграции, ругается на id записи

Задача: В таблице есть поле содержащие тело статьи(текст). В этом тексте есть ссылки старого формата, нужно пройтись регуляркой и заменить формат. К сожалению на проекте версия MySQL 5.7, а функции для регулярной замены (REGEXP_REPLACE) была добавлена в 8.0. Ещё нужно учитывать что на проекте все работы с БД происходят через миграции Phinx(из Cake).

Было принято решение сделать выборку по всем статьям(их +- 6к) и перебрать тексты статьи и заменить ссылки силами php, в цикле.

Вроде бы всё должно работать но почему то БД ругается на id. Пометка: 27 id - это id первой записи в таблице.

Ошибка при выполнении миграции

public function up()
{
    $rows = $this->fetchAll("SELECT id, body from blogposts");

    foreach ($rows as $post )
    {
        $post['body'] = preg_replace('/sustainability/(\d+/\d+/\d+)/(.+)', '/sustainability/$2', $post['body']);
        $postId = $post['id'];
        $postBody = $post['body'];
        $this->query( "UPDATE blogposts SET body = $postBody  WHERE id = $postId ")->save();
    }
}

Прошу подсказать и разобраться в чем может быть проблема?


Ответы (2 шт):

Автор решения: teran

обычно такое делается в виде

$bpt = TableRegistry::getTableLocator()->get('BlogPosts');
$items = $bpt->find('all')->select(['id', 'body'])->toArray();
foreach($items as $p){
     $p->body = preg_replace("/..../", "...", $p->body);
}
$pbt->saveAll($items);

при этом учесть, что регулярное выражение должно иметь вид /expr/flags, то есть имеет ограничители в начале и конце. То есть не /sustainability/(\d+/\d+/\d+)/(.+) а ~/sustainability/(\d+/\d+/\d+)/(.+)~. (Обычно в качестве ограничителей выступают /, но поскольку эти символы у вас используются, то лучше заменить на ~ или #, в основном используют эти 3 варианта)

для таких целей имхо лучше не миграции использовать а Command сгенерить.

Документация к phinx, велит делать примерно следующим образом

$rows = $this->fetchAll("SELECT id, body from blogposts WHERE body like '%/sustainability/%'");
$builder = $this->getQueryBuilder();
foreach($rows as $row){
    $body = ...;
    if(!$changed) continue;

    $builder->update('blogposts')
            ->set(['body', $body])
            ->where(['id' => $row->id)
            ->execute();
    
}
   

Однако, да, getQueryBuilder в конечном итоге сводится к PdoAdapter::getDecoaratedConnection, возвращающий экземпляр \Cake\DatabaseConnection.

Интересно, что в этой ситуации методы execute() и query() имеют только один параметр - текст запроса и не позволяют передавать параметры.

Возможно, альтернативой будет получить нижележащий экземпляр PDO и выполнить запросы через привычные подготовленные выражения.
Получить вроде можно через $this->getAdapter()->getConnection(). А дальше обычные $connection->prepare() и execute() с передачей параметров.

→ Ссылка
Автор решения: Animov

Удалось решить.

$rows = $this->fetchAll("SELECT id, body from blogposts");

    foreach ($rows as $post )
    {
        $post['body'] = preg_replace('~/sustainability/(\d+/\d+/\d+)/(.+)~', '/sustainability/$2', $post['body']);
        $post['body'] = addslashes($post['body']);
        $postId =  $post['id'];
        $postBody = $post['body'];
        $this->execute( "UPDATE blogposts SET body = '$postBody'  WHERE id = $postId ");

Добавил обработку экранирования, т.к в статьях содержаться апострофы свойственные для английского языка.

$post['body'] = addslashes($post['body']);

Так же в запросе на UPDATE переменную содержащую текст необходимо обромить в ковычки.

$this->execute("UPDATE blogposts SET body = '$postBody'  WHERE id = $postId")
→ Ссылка