18 заметок с тегом

yii2

Yii2 ошибка «The file or directory to be published does not exist: /path/to/project/vendor/bower/jquery/dist

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

rm -rf ~/.composer/cache
rm -rf /path/to/project/vendor
composer global require "fxp/composer-asset-plugin:^1.2.0"
composer install

Здесь можно вспомнить как установить composer

2017   composer   composer install   yii2

Реализовать ajax форму с помощью ActiveForm в Yii2

Саму форму необходимо обернуть в ActiveForm:

$form = ActiveForm::begin([
        'id' => 'form',
        'enableClientValidation' => true,
        'enableAjaxValidation'   => false,
        'method' => 'post',
    ]);
    // сама форма, инпуты, селекты и т.д...
    ...
ActiveForm::end();

Также необходимо написать небольшой кусок кода в виде jquery функции:

$(document).on("beforeSubmit", "#form", function (e) {
    var form = $(this);
    var formData = form.serialize();
    $.ajax({
        url: form.attr("action"),
        type: form.attr("method"),
        data: formData,
        success: function (response) {
            alert(response.message)
        },
        error: function () {
            // действие на случай ошибки
        }
    });

}).on('submit', function(e){
    e.preventDefault();
});

UPD
Если в форме есть input[type=file], т. е. в форме должны передаваться файлы, тогда добавим глобальную функцию для сериализации всех полей формы, в том числе и приложенных файлов.

(function($) {
    // сериализация данных формы с учетом передачи файлов
    $.fn.serializefiles = function() {
        var obj = $(this);
        var formData = new FormData();
        $.each($(obj).find("input[type='file']"), function(i, tag) {
            $.each($(tag)[0].files, function(i, file) {
                formData.append(tag.name, file);
            });
        });
        var params = $(obj).serializeArray();
        $.each(params, function (i, val) {
            formData.append(val.name, val.value);
        });
        return formData;
    };
})(jQuery);

И соответственно, новый метод $.ajax будет выглядеть так:

$(document).on("beforeSubmit", "#form", function (e) {
    var form = $(this);
    // вот здесь собираем поля формы
    var formData = form.serializefiles();
    $.ajax({
        url: form.attr("action"),
        type: form.attr("method"),
        data: formData,
        success: function (response) {
            alert(response.message)
        },
        error: function () {
            // действие на случай ошибки
        }
    });

}).on('submit', function(e){
    e.preventDefault();
});
2017   activeform   ajax   jquery   yii2

Ограничить количество запросов к приложению в Yii2

Добавьте в behavior след.правило:

public function behaviors()
{
    return [
        'rateLimiter' => [
            'class' => yii\filters\RateLimiter::className(),
            'enableRateLimitHeaders' => false, // не передавать в хедере оставш. кол-во запросов и время
            'errorMessage' => 'Слишком много запросов',
            'only' => ['index'], // Определить экшн для применения
            'user' => new UrlRateLimiter(),
        ],
    ];
}

UrlRateLimiter — описан в отдельном классе, для удобства и представляет следующий код:

namespace api\models;

use common\models\User;
use yii\filters\RateLimitInterface;

/**
 * Class UrlRateLimiter
 * Ключ для кеша строится по URL-запроса
 *
 * @package api\models
 */
class UrlRateLimiter extends User implements RateLimitInterface
{
    public $rateLimit = 6;
    public $allowance;
    public $allowance_updated_at;
    
    /**
     * Кол-во разрешенных запросов в секунду
     *
     * @param \yii\web\Request $request
     * @param \yii\base\Action $action
     * @return array
     */
    public function getRateLimit($request, $action)
    {
        // rateLimit - кол-во
        // 10 - это секунды
        return [$this->rateLimit, 10];
    }
    
    public function loadAllowance($request, $action)
    {
        $cache = \Yii::$app->cache;
        $key = sha1(serialize($request->url));
        return [
            $cache->get('user.ratelimit.ip.allowance.' . $key),
            $cache->get('user.ratelimit.ip.allowance_updated_at.' . $key),
        ];
    }
    
    /**
     * Метод сохранит в кеш
     * @param \yii\web\Request $request
     * @param \yii\base\Action $action
     * @param int $allowance
     * @param int $timestamp
     */
    public function saveAllowance($request, $action, $allowance, $timestamp)
    {
        $cache = \Yii::$app->cache;
        $key = sha1(serialize($request->url));
        
        $cache->set('user.ratelimit.ip.allowance.' . $key, $allowance);
        $cache->set('user.ratelimit.ip.allowance_updated_at.' . $key, $timestamp);
        
    }
}

Опубликован код здесь

2017   rateLimiter   yii2

В Yii2 behavior вынести проверку на Ajax

Чтобы в каждом экшне не делать проверку на Yii::$app->request->isAjax делаем элегантное решение в behavior:

public function behaviors()
{
    return [
        'access' => [
            'class' => yii\filters\AccessControl::className(),
            'only' => ['details'],
            'rules' => [
                // allow only AJAX requests using the post or get Method
                [
                    'allow' => true,
                    'verbs' => ['GET'],
                    'matchCallback' => function () {
                        return Yii::$app->request->getIsAjax();
                    },
                ],
            ],
        ],
    ];
}
2017   AccessControl   ajax   isAjax   yii2

PostgreSQL: группируем данные и оборачиваем в массив

Вдруг стоит задача получить сгруппированный список, например из связанной таблицы, да при том оформить построчно данные в виде массива, как-то так:

id author book_ids
1 1 [1, 4]
2 2 3
3 3 [2, 5, 7]

Ну давайте посмотрим, что можно с тим сделать в PostgreSQL.

Вот представим есть 3 таблицы:

  • Книги, `books`
  • Авторы, `authors`
  • Связанная таблица Книга — Автор, `book_author`.

Опишем данные:

Табл `books`:

serial id
string name, Название книги
timestamp create_date, Дата выпуска

id name create_date
1 Преступление и наказание ...
2 Анна Каренина ...
3 Мёртвые души ...
4 Братья Карамазовы ...

Табл `authors`:

serial id
string name, Имя автора
string lastname, Фамилия
string middlename, Отчество (необязательное)
datetime birth_date, День рождения автора

id name lastname middlename birth_date
1 Федор Достоевский Михайлович ...
2 Николай Гоголь ...
3 Лев Толстой Николаевич ...

Соответственно, у книги может быть несколько авторов, и автор может написать несколько книг, потому и нужна связанная таблица:

Табл `book_author`:

serial id
int book_id
int author_id

id book_id author_id
1 1 1
2 2 3
3 3 2
4 4 1

Задача: получить сгруппированный список Авторов и написанных ими книг в виде массива, т.е:

id author book_ids
1 1 [1, 4]
2 2 3
3 3 2

SQL выражение может быть таким:

SELECT array_to_json(array_agg(concat(book_id))) as book_ids, author_id
FROM "book_author"
GROUP BY author_id;

Если же составлять такой запрос в Yii2, то выглядить это может вот так:

$query = BookAuthor::find()
    ->select([
        new Expression("array_to_json(array_agg(concat(".BookAuthor::tableName().".book_id))) as book_ids"),
        BookAuthor::tableName().'.author_id',
    ])
    ->joinWith('author')
    ->groupBy([BookAuthor::tableName().'.author_id'])
    ->indexBy('author_id');

Либо можно передать в провайдер:

$dataProvider = new ActiveDataProvider([
    'query' => $query->all(),
]);

Стоит обратить внимание, что я добавил жадной загрузки в виде joinWith('author'). Эти связанные данные лучше одним запросом достать, если планируется делать вывод во представлении всего списка.

PostgreSQL: преобразование типа поля VARCHAR в INTEGER

Стандартными средствами sql это можно сделать так:

ALTER TABLE table_name ALTER COLUMN column_name TYPE integer USING (column_name::integer);

А миграцией в Yii2 можно сделать так:

public function safeUp()
{
    $this->execute('alter table {{%table_name}} alter column column_name TYPE integer USING (column_name::integer);');
}

В итоге мы получим преобразованное поле Integer.

UPD, в некоторых случаях необходимо сделать:

update "table_name" set column_name=null;
2017   alter table   PostgreSQL   sql   yii2

Проверить таблицу БД на существование в Yii2

Бывает так, что надо сходить в какую-либо таблицу по определенным критериям, но заранее неизвестно, существует такая таблица в БД или нет.
Тогда необходимо выполнить следующую проверку:

if (\Yii::$app->db->getTableSchema('{{%table_name}}', true) !== null) {
    // какой-то код для работы с данной таблицей...
}

Более подробно в документации yii\db\Schema

2017   AR   getTableSchema   Schema   yii2

Как побороть ошибку Bad Request (#400): Не удалось проверить переданные данные

Такая ошибка часто возникает в случае асинхронного запроса. Все дело в том, что где-то в родительском контроллере включена проверка на Csrf.

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

namespace app\controllers;


use Yii;
use yii\web\Controller;
use yii\filters\VerbFilter;

class NewController extends Controller
{
    public $enableCsrfValidation = false;

    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
        ];
    }
    
    ...
}

Вас как раз должно интересовать свойство $enableCsrfValidation.

2017   Bad Request   enableCsrfValidation   http400   php   yii2
2017   json   php   yii2

Получить чистый sql-запрос из AR и AQ в Yii2

Собираем запрос, используя Active Record или Active Query и чтобы убедиться в правильности запроса смотрим на сам SQL.

// собираем AR
$orders = Order::find()->where(['status' => 1])->orderBy('date ASC');

// Получим sql в чистом виде:
$orders->createCommand()->getRawSql();

И увидим результат:

SELECT * FROM "order" WHERE "status"=1 ORDER BY "date"
2017   active query   active record   AR   php   sql   yii2
Ctrl + ↓ Ранее