CakePHP 3: Сортировка пагинатора по языкам (с поведением i18n translate)

У меня есть таблица со всеми записями, включая все переводы на нескольких языках: Как я могу создать ссылки сортировки нумерации страниц в переведенных полях? (Торт 3.1.6)

Резюме: Это не работает, я не могу отсортировать переводы таким образом:

$this->Paginator->sort('_translations.es.title', 'Spanish')

Полная версия:

| Title ENGLISH    | Title SPANISH    | Title GERMAN    |  = pagination sort links
| ---------------- | ---------------- | --------------- |
| Christmas        | Navidad          | Weihnachten     |
| Spring           | Primavera        | Frühling        |
| ...

Итак, вот моя упрощенная тестовая установка:

Таблица Статьи имеет только одно поле title для перевода.
i18n Таблица по умолчанию настроена как описано в книге.

Запеченный Table класс /src/Model/Table/ArticlesTable.php, добавлен Translate Behavior:

public function initialize(array $config) {
  // ... default config (removed in this post to simplify code)
  $this->addBehavior('Translate', ['fields' => ['title']]);  // added this line
}

Запеченный Entity класс /src/Model/Entity/Article.php, добавлен TranslateTrait:

namespace App\Model\Entity;
use Cake\ORM\Behavior\Translate\TranslateTrait; // added this line
use Cake\ORM\Entity;
class Article extends Entity {
  protected $_accessible = [
    '*' => true,
    'id' => false,
  ];
  use TranslateTrait; // added this line
}

Запеченный Контроллер /src/Controller/ArticlesController.php, измененный следующим образом:

namespace App\Controller;
use App\Controller\AppController;
class ArticlesController extends AppController {
  public $paginate = [
    'sortWhitelist' => [  // Allow pagination sort on this fields:
      'title',
      '_translations.es.title',
      '_translations.de.title'
    ]
  ];
  public function index() {
    $query = $this->Articles->find('translations'); // Retrieve All Translations
    $this->set('articles', $this->paginate($query));
  }
}

Запеченный Просмотр /src/Template/Articles/index.ctp, измененный / упрощенный:

<table>
  <tr>
    <th><?= $this->Paginator->sort('title', 'English') ?></th>
    <th><?= $this->Paginator->sort('_translations.es.title', 'Spanish') ?></th>
    <th><?= $this->Paginator->sort('_translations.de.title', 'German') ?></th>
  </tr>
<?php foreach ($articles as $article): ?>
  <tr>
    <td><?= h($article->title) ?></td>
    <td><?= h($article->_translations['es']->title) ?></td>
    <td><?= h($article->_translations['de']->title) ?></td>
  </tr>
<?php endforeach; ?>
</table>

Переводы в таблице отображаются правильно, но сортировка по переведенным полям невозможна. Когда я нажимаю на ссылки для перевода переводов на страницы, я получаю следующую ошибку:

SQLSTATE [42S22]: столбец не найден: 1054 Неизвестный столбец '_translations.es' в 'предложении порядка'

SQL Query:
SELECT Articles.id AS `Articles__id`,
       Articles.title AS `Articles__title`,
       Articles.created AS `Articles__created`,
       Articles.modified AS `Articles__modified`
FROM articles Articles 
ORDER BY _translations.es asc LIMIT 20 OFFSET 0

В этом аналогичном вопросе используется формат Posts_title_translation.content - Я понятия не имею, откуда это, но я тоже пробовал это (конечно, я также добавил варианты имен полей в белый список пагинатора):

$this->Paginator->sort('Articles_title_translation', 'Spanish')
$this->Paginator->sort('Articles_title_translation.es', 'Spanish')
$this->Paginator->sort('Articles_title_translation.content', 'Spanish')
$this->Paginator->sort('Articles_title_translation.es.content', 'Spanish')

Ни один из них не работает ... (очевидно)

Как я могу отсортировать элементы таблицы по i18n-переводам поля заголовка?


person Oops D'oh    schedule 23.12.2015    source источник


Ответы (1)


Для сортировки по переведенным полям требуются объединения

Как правило, вы можете сортировать только те поля, которые объединены в основной запрос!

Переведенные поля объединяются только с текущим языковым стандартом, отличным от стандартного.

По умолчанию переведенные поля объединяются только в том случае, если текущий языковой стандарт (I18n::locale()) не соответствует языку по умолчанию (I18N::defaultLocale(), intl.default_locale), т.е. когда действительно есть необходимость что-то перевести.

После изменения текущего языкового стандарта на нестандартный

I18n::locale('de');
$query = $this->Articles->find('translations');
// ...

поведение translate будет содержать ассоциации с переведенным контентом, и отсюда происходит TableAlias_field_translation псевдоним, поведение создает hasOne ассоциации для каждого переведенного поля с использованием этой схемы именования.

Эти поля затем можно использовать для разбивки на страницы, однако это будет объединяться только в одной локали за раз!

Убедитесь, что пагинатор использует правильные поля.

Поскольку ассоциации не всегда содержатся, вам необходимо принять соответствующие меры, чтобы средство разбиения на страницы использовало правильные поля в зависимости от локали. Что-то вроде этого (обратите внимание, что это просто непроверенный пример кода для иллюстрации)

public function index()
{
    $sort = $this->request->query('sort');
    if ($sort) {
        $fieldMap = [
            'Articles_title_translation.content' => 'Articles.title'
        ];
        if (
            isset($fieldMap[$sort]) &&
            $this->Articles->locale() ===
                $this->Articles->behaviors()->get('Translate')->config('defaultLocale')
        ) {
            $this->request->query['sort'] = $fieldMap[$sort];
        }
    }

    $query = $this->Articles->find('translations');
    $this->set('articles', $this->paginate($query));
}

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

Присоединение и сортировка всех переводов / языков поля

Вышесказанное относится к сортировке по одному полю, которое может или не может быть переведено. Наличие всех переводов поля, доступных для сортировки, выходит за рамки возможностей преобразования. Хотя поведение загружает все переводы, оно делает это с помощью ассоциации hasMany, т. Е. С использованием отдельного запроса, и поэтому они не могут использоваться для сортировки. Присоединение ко всем переводам необходимо производить вручную.

Это может быть что-то для запроса функции, но я не уверен, что если это обычный вариант использования, который оправдывает такие изменения ядра, вы можете открыть вопрос на GitHub или спросите на IRC.

При этом, вот базовый пример, расширенное поведение перевода, которое в значительной степени делает то, что делают TranslateBehavior::setupFieldAssociations() и TranslateBehavior::beforeFind(), только с небольшими изменениями. Поведение принимает параметр locales, который необходимо указать со всеми языковыми стандартами, которые должны быть объединены, поскольку они не могут быть вычислены автоматически.

src / Model / Table / ArticlesTable.php

// Remove $this->addBehavior('Translate', ['fields' => ['title']]);
// and load the custom behavior instead (otherwise there will be an
// error about "duplicate translation finders"

$this->addBehavior('MyTranslate', [
    'fields' => ['title'],
    'locales' => ['es', 'de']
]);

src / Model / Behavior / MyTranslateBehavior.php

namespace App\Model\Behavior;

use Cake\ORM\Behavior\TranslateBehavior;
use Cake\ORM\Query;
use Cake\ORM\Table;

class MyTranslateBehavior extends TranslateBehavior
{
    protected $_associations = [];

    public function __construct(Table $table, array $config)
    {
        $config += [
            'locales' => []
        ];

        parent::__construct($table, $config);
    }

    public function setupFieldAssociations($fields, $table, $model, $strategy)
    {
        parent::setupFieldAssociations($fields, $table, $model, $strategy);

        $alias = $this->_table->alias();
        $tableLocator = $this->tableLocator();
        $locales = $this->config('locales');

        $this->_associations = [];
        foreach ($fields as $field) {
            foreach ($locales as $locale) {
                $name = $alias . '_' . $field . '_translation_' . $locale;

                if (!$tableLocator->exists($name)) {
                    $fieldTable = $tableLocator->get($name, [
                        'className' => $table,
                        'alias' => $name,
                        'table' => $this->_translationTable->table()
                    ]);
                } else {
                    $fieldTable = $tableLocator->get($name);
                }

                $conditions = [
                    $name . '.locale' => $locale,
                    $name . '.model' => $model,
                    $name . '.field' => $field
                ];

                $this->_table->hasOne($name, [
                    'targetTable' => $fieldTable,
                    'foreignKey' => 'foreign_key',
                    'joinType' => 'LEFT',
                    'conditions' => $conditions,
                    'propertyName' => $field . '_translation_' . $locale
                ]);

                $this->_associations[] = $name;
            }
        }
    }

    public function findTranslations(Query $query, array $options)
    {
        $query->contain($this->_associations);
        return parent::findTranslations($query, $options);
    }
}

То, что он делает, должно быть относительно легко понять: он просто создаст и будет содержать hasOne ассоциации для всех полей во всех настроенных региональных стандартах. Псевдонимы будут использовать формат TableAlias_field_translation_locale, например Articles_title_translation_es, который затем необходимо использовать в белом списке сортировки и ссылках сортировки пагинатора.

Следует отметить, что при объединении новых полей, вероятно, потребуется порядок сортировки по умолчанию, поскольку в противном случае он может отсортировать подзапрос, используемый при запросе переводов, иначе, чем основной запрос, что приведет к неправильным переводам. извлекается!

public $paginate = [
    'order' => ['Articles.title' => 'ASC']
    'sortWhitelist' => [
        'Articles.title',
        'Articles_title_translation_de.content',
        'Articles_title_translation_es.content'
    ]
];
$this->Paginator->sort('Articles.title', 'English');
$this->Paginator->sort('Articles_title_translation_es.content', 'Spanish');
$this->Paginator->sort('Articles_title_translation_de.content', 'German');

Смотрите также

person ndm    schedule 23.12.2015