Для получения полного доступа
зарегистрируйтесь.
RSS

Все сниппеты с тэгом «yii2»



Gravatar image
zabachok
  • Репутация: 2
  • Сниппеты: 1
  • Ревизии: 0
echo $form->field($model, 'is_bot')->radioList(['' => 'Все', false => 'Люди', true => 'Боты'], [
            'itemOptions' => ['class' => 'd-none', 'labelOptions' => ['class' => 'btn btn-primary']],
            'class' => 'btn-group',
            'data-toggle' => 'buttons',
        ])->label(false);

Gravatar image
Insolita
  • Репутация: 7
  • Сниппеты: 3
  • Ревизии: 1

При сохранении груповых данных, зачастую требуется сопоставить, то-что у нас уже хранится в БД и то-что пришло с формы, для этого приходится сверять элименты по уникальному полю, данный снипет, из одной незамысловатой строки, вернет массив с уникальными ключами. Затем можно уже проверять на isset($models_by_id[$id]) или array_diff_key, ну и т.д.

$models_by_id=Model::find()->indexBy('id')->all(); //yii 2 path @mista twista

$models=Model::model()->findAll(); //yii 1
$models_by_id=array_combine(array_keys(CHtml::listData($models, 'id', 'id')),$models);
$models_by_id=array_combine(array_column($model,'id'),$model); //since php5.5

Gravatar image
egorsmkv
  • Репутация: 9
  • Сниппеты: 1
  • Ревизии: 0

Стандартный класс LinkPager генерирует HTML-код постраничной навигации, который неправильно отображается с Bootstrap 4. Этот сниппет исправляет эту проблему.

<?php

namespace app\components;

use Yii;
use yii\helpers\Html;
use yii\widgets\LinkPager;

class BootstrapLinkPager extends LinkPager
{
    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        // In Bootstrap 4 no div's "next" and "prev", so you need to overwrite the default values
        $this->prevPageCssClass = 'page-item';
        $this->nextPageCssClass = 'page-item';

        // Change the location and size of block
        // https://v4-alpha.getbootstrap.com/components/pagination/#alignment
        // https://v4-alpha.getbootstrap.com/components/pagination/#sizing
        $this->options['class'] = 'pagination justify-content-center';

        // Change standard arrows "«" and "»"
        $this->nextPageLabel = Yii::t('app', 'Next');
        $this->prevPageLabel = Yii::t('app', 'Previous');

        // Default div for links
        $this->linkOptions['class'] = 'page-link';
    }

    /**
     * @inheritdoc
     */
    public function run()
    {
        if ($this->registerLinkTags) {
            $this->registerLinkTags();
        }

        if ($this->pagination->getPageCount() > 1) {
            echo Html::tag('nav', $this->renderPageButtons());
        }
    }

    /**
     * @inheritdoc
     */
    protected function renderPageButton($label, $page, $class, $disabled, $active)
    {
        $options = ['class' => empty($class) ? 'page-item' : $class];
        $linkOptions = $this->linkOptions;

        if ($active) {
            Html::addCssClass($options, $this->activePageCssClass);
        }

        if ($disabled) {
            Html::addCssClass($options, $this->disabledPageCssClass);
            $linkOptions['tabindex'] = '-1';
        }

        return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), $linkOptions), $options);
    }
}

Использовать просто: вместо LinkPager при вызове используйте BootstrapLinkPager.

Gravatar image
Insolita
  • Репутация: 7
  • Сниппеты: 3
  • Ревизии: 1
<?php
namespace common\tests;

use Codeception\Util\Debug;
use yii\console\Application;
use yii\db\ActiveQuery;
use yii\db\Connection;
use yii\helpers\ArrayHelper;
use yii\helpers\Console;
use yii\helpers\VarDumper;

/**
 * Useful set for performance optimization in console scripts, and test researches
 *
 * Trait ProfilingInspectorTrait
 *
 * @package common\tests
 */
trait ProfilingInspectorTrait
{
    
    /**
     * @param \yii\db\Query|ActiveQuery $query
     * @param \yii\db\Connection        $db
     */
    protected function showQuery(\yii\db\Query $query, Connection $db)
    {
        $sql = $query->prepare($db->queryBuilder)->createCommand($db)->rawSql;
        $this->resolvedOutput($sql, __FUNCTION__);
    }
    
    /**
     * @param \yii\db\Query|ActiveQuery $query
     * @param \yii\db\Connection        $db
     */
    protected function explainQuery(\yii\db\Query $query, Connection $db)
    {
        $sql = $query->prepare($db->queryBuilder)->createCommand($db)->rawSql;
        $sql = 'EXPLAIN: ' . implode(PHP_EOL, $db->createCommand('EXPLAIN ' . $sql)->queryColumn('QUERY PLAN'));
        $this->resolvedOutput($sql, __FUNCTION__);
    }
    
    /**
     * @param \yii\db\Query|ActiveQuery $query
     * @param \yii\db\Connection        $db
     */
    protected function analyzeQuery(\yii\db\Query $query, Connection $db)
    {
        $sql = $query->prepare($db->queryBuilder)->createCommand($db)->rawSql;
        $sql = 'ANALYZE: ' . implode(PHP_EOL, $db->createCommand('EXPLAIN ANALYZE ' . $sql)->queryColumn('QUERY PLAN'));
        $this->resolvedOutput($sql, __FUNCTION__);
    }
    
    /**
     * Show execution time for specified callback and time elapsed from app start
     * Return real function result
     *
     * @example
     *         $result=$this->timeIt(function(){
     *         $data = User::find()->where(['<','id', 100])->all();
     *         return ArrayHelper::map($data,'id','name');
     *         });
     *
     * @param \Closure $function
     * @param string   $comment Additional log comment
     *
     * @return mixed
     */
    protected function timeIt(\Closure $function, string $comment = '')
    {
        $mStart = microtime(true);
        $result = $function();
        $mEnd = microtime(true);
        $this->resolvedOutput(
            [
                'timeDelta' => ($mEnd - $mStart),
                'elapsedTime'=>\Yii::getLogger()->elapsedTime
            ],
            __FUNCTION__ . ':' . $comment
        );
        return $result;
    }
    
    /**
     * Show execution time, memory usage, and peek memory check for specified callback
     * Return real function result
     *
     * @example
     *           $result1=$this->inspectIt(function(){
     *           $data = User::find()->where(['<','id', 100])->all();
     *           return ArrayHelper::map($data,'id','name');
     *           },'Object result');
     *           $result2 = $this->inspectIt(function(){
     *           $data = User::find()->where(['<','id', 100])->asArray()->all();
     *           $result = ArrayHelper::map($data,'id','name');
     *           unset($data);
     *           return $result;
     *           },'As Array result');
     *
     * @param \Closure $function
     * @param string   $comment Additional log comment
     *
     * @return mixed
     */
    protected function inspectIt(\Closure $function, string $comment = '')
    {
        $start = \Yii::getLogger()->elapsedTime;
        $start_memory = memory_get_usage();
        $start_peek = memory_get_peak_usage();
        $result = $function();
        $end_memory = memory_get_usage();
        $end_peek = memory_get_peak_usage();
        $end = \Yii::getLogger()->elapsedTime;
        $this->resolvedOutput(
            [
                'timeDelta' => ($end - $start),
                'memory'    => [
                    'from'                => \Yii::$app->formatter->asSize($start_memory),
                    'to'                  => \Yii::$app->formatter->asSize($end_memory),
                    'delta'               => \Yii::$app->formatter->asSize(($end_memory - $start_memory)),
                    'peekMemoryIncreased' => ($end_peek > $start_peek) ? \Yii::$app->formatter->asSize($start_peek)
                        . ' +' . \Yii::$app->formatter->asSize($end_peek - $start_peek) : 'no',
                ],
            ],
            __FUNCTION__ . ':' . $comment
        );
        return $result;
    }
    
    /**
     * Show profile log for specified callback
     * Return real function result
     *
     * @example
     *         $result=$this->profileIt(function(){
     *         $data = User::find()->where(['<','id', 100])->all();
     *         return ArrayHelper::map($data,'id','name');
     *         });
     *
     * @param \Closure $function
     * @param string   $comment Additional log comment
     *
     * @return mixed
     */
    protected function profileIt(\Closure $function, string $comment = '')
    {
        $id = uniqid('profile_');
        \Yii::beginProfile($id, __FUNCTION__ . ':' . $id);
        $result = $function();
        \Yii::endProfile($id, __FUNCTION__ . ':' . $id);
        $profile = \Yii::getLogger()->getProfiling([]);
        $map = ArrayHelper::getColumn($profile, 'info');
        $profile = array_slice($profile, array_search($id, $map));
        unset($map);
        $this->resolvedOutput($profile, __FUNCTION__ . ':' . $id . ':' . $comment);
        return $result;
    }
    
    /**
     * @param $message
     */
    protected function resolvedOutput($message, $subj = ''): void
    {
        $divider = '============= ' . $subj . ' ===============';
        if (YII_ENV_TEST === true) {
            Debug::debug($divider);
            Debug::debug($message);
        } elseif (\Yii::$app instanceof Application) {
            Console::output(Console::ansiFormat($divider . PHP_EOL, [Console::FG_BLUE]));
            Console::output(Console::ansiFormat(VarDumper::export($message) . PHP_EOL, [Console::FG_GREEN]));
        } else {
            \Yii::trace($message, get_called_class().':'.$subj);
        }
    }
   
}
Gravatar image
Insolita
  • Репутация: 7
  • Сниппеты: 3
  • Ревизии: 1
<?php

use yii\db\Migration;

class m170105_004305_add_fts extends Migration
{
    public function safeUp()
    {

        /*
         * PREPARE SEARCH CONFIGURATION
         *----------------------------
         */
        $this->getDb()->createCommand(
            '
           CREATE TEXT SEARCH DICTIONARY ispell_ru (
           template  =   ispell,
           dictfile  =   ru,
           afffile   =   ru,
           stopwords =   russian
           );
           '
        )->execute();
        $this->getDb()->createCommand(
            '
           CREATE TEXT SEARCH DICTIONARY ispell_en (
           template  = ispell,
           dictfile  = en,
           afffile   = en,
           stopwords = english
           );
           '
        )->execute();
        $this->getDb()->createCommand('CREATE TEXT SEARCH CONFIGURATION ru ( COPY = russian );')->execute();
        $this->getDb()->createCommand(
            'ALTER TEXT SEARCH CONFIGURATION ru
           ALTER MAPPING
           FOR word, hword, hword_part
           WITH ispell_ru, russian_stem;
           '
        )->execute();
        $this->getDb()->createCommand(
            'ALTER TEXT SEARCH CONFIGURATION ru
           ALTER MAPPING
           FOR asciiword, asciihword, hword_asciipart
           WITH ispell_en, english_stem;'
        )->execute();
        $this->getDb()->createCommand('SET default_text_search_config = \'ru\';')->execute();
       
        /** ADD tsvector column **/
        $this->getDb()->createCommand(
            '
           ALTER TABLE {{%tovar}} ADD COLUMN fts tsvector;
        '
        )->execute();
        $this->getDb()->createCommand(
            '
           UPDATE {{%tovar}} SET fts=
setweight( coalesce( to_tsvector(\'ru\', [[name]]),\'\'),\'A\') || \' \' ||
setweight( coalesce( to_tsvector(\'ru\', [[description]]),\'\'),\'B\') || \' \';
        '
        )->execute();
        $this->getDb()->createCommand('create index fts_index on {{%tovar}} using gin (fts);')->execute();
        
        /**
         * ---   ADD AUTO FILL fts TRIGGER ON INSERT NEW RECORD
         * (in my case 'on update' trigger not neccessary)
        **/
        $this->getDb()->createCommand(
            '
            CREATE FUNCTION fts_vector_update() RETURNS TRIGGER AS $$
BEGIN
   NEW.fts=setweight( coalesce( to_tsvector(\'ru\', NEW.name),\'\'),\'A\') || \' \' ||
			setweight( coalesce( to_tsvector(\'ru\', NEW.description),\'\'),\'B\') || \' \';
			RETURN NEW;
END;
$$ LANGUAGE \'plpgsql\';
CREATE TRIGGER tovar_fts_update BEFORE INSERT ON {{%tovar}}
FOR EACH ROW EXECUTE PROCEDURE fts_vector_update();
        '
        )->execute();
    }

    public function safeDown()
    {
        $this->dropIndex('fts_index', '{{%tovar}}');
        $this->dropColumn('{{%tovar}}', 'fts');
        $this->getDb()->createCommand('DROP TRIGGER tovar_fts_update ON {{%tovar}}')->execute();
        $this->getDb()->createCommand('DROP FUNCTION IF EXISTS fts_vector_update()')->execute();
    }
}

Gravatar image
Insolita
  • Репутация: 7
  • Сниппеты: 3
  • Ревизии: 1
 
    public function findSuggest(string $query, int $cat = null): array
    {
        $query = $this->prepareQuery($query);
        $tQuery = (new Query())->from('{{%tovar}}')
                               ->select([
                                   '{{%tovar}}.id',
                                   '{{%tovar}}.name',
                                   '{{%tovar}}.slug',
                                   '{{%category}}.name as category',
                                   new Expression("ts_rank({{%tovar}}.fts,plainto_tsquery('ru', :q)) as rank"),
                                        ])
                               ->leftJoin('{{%category}}','{{%tovar}}.category_id={{%category}}.id')
                               ->where(new Expression("{{%tovar}}.fts  @@ plainto_tsquery('ru', :q)", [':q' => $query]))
                               ->limit(10)
                               ->orderBy(['rank' => SORT_DESC]);
        if($cat > 0){
            $tQuery->andWhere(['{{%tovar}}.category_id'=>$cat]);
        }
        return $tQuery->all();
    }
Gravatar image
Tutik Alexsandr
  • Репутация: 2
  • Сниппеты: 6
  • Ревизии: 0

Бывают такие задачи, когда нужно сделать запуск скрипта один раз в минуту и чтобы повторный скрипт не выполнялся до тех пор, пока первый скрипт не отработает полностью. Решений много (можно помечать запись(и) в БД и не брать их при след запуске, можно блокироваь фаил для процесса и это самый примитивный способ. На самом деле кода тут под Yii2 совсем мало 1 функция.. Как это работает: создаем файл и делаем на этот файл LOCK, пока процесс работает файл будет заблокирован стоит процессу умереть LOCK с файла спадет сам.

Создаем BaseController для всех наших бедующих контроллеров

<?php

namespace app\commands\base;

use yii\console\Controller;
use Yii;

class BaseController extends Controller
{
    public $lockHandle;
    protected function lockProcess($pid)
    {
        $path = Yii::getAlias('@app/runtime/logs/'.$pid.'.txt');
        
        if(!flock($this->lockHandle = fopen($path, 'w'), LOCK_EX | LOCK_NB)){
            echo "Already runninng\n";
            exit;
        }
        fwrite($this->lockHandle,'run');
    }

    protected function unlockProcess()
    {
        flock($this->lockHandle, LOCK_UN);
        fclose($this->lockHandle);
    }
}

Как пользоватся? наследуем свой класс от BaseController и используем методы

$this->lockProcess('test-pid1');
// тут какой-то тяжелоый код, который любит долго работать
$this->unlockProcess()
Gravatar image
kotchuprik
  • Репутация: 13
  • Сниппеты: 4
  • Ревизии: 0

Небольшой рецепт без кеширования (в комментах возможно будут элегантные решения для быстродействия) для решения проблемы запрета перехода поискового бота по ссылке. Теория тут. Сниппет скорее прототип, использовать надо с мозгами, кроме кеширования, нет защиты от уже ранее добавленного rel nofollow.

'components' => [
// ...
  'view' => [
    'on afterRender' => function ($e) {
      $layoutPath = 'layouts/main';
      if (strstr($e->viewFile, $layoutPath)) {
          $skip = 'mydomain.ru';
          $e->output = preg_replace_callback(
              '/(<a[^>]+?)>/is', function ($match) use ($skip, $skip2) {
              if (!($skip && strpos($match[1], $skip) !== false) &&
                  strpos($match[1], 'rel=') === false &&
                  strpos($match[1], 'http') !== false
              ) {
                  return $match[1] . ' rel="nofollow">';
              }

              return $match[0];
          }, $e->output);
      }
    },
  ],
// ...
],

Gravatar image
kotchuprik
  • Репутация: 13
  • Сниппеты: 4
  • Ревизии: 0

На случай, когда необходимо получить "сырой" SQL запрос вызова Query модели, но debug bar или подробные логи не используются при разработке.

function dumpQuery(\yii\db\ActiveQuery $query)
{
    echo '<pre>';
    var_export($query->prepare(\Yii::$app->db->queryBuilder)->createCommand(\Yii::$app->db)->rawSql);
    echo '</pre>';
    
    Yii::$app->end();
}

Использование:

dumpQuery(Model::find()->where(['foo' => 'bar'])->orderBy(['awesome' => SORT_ASC]));

Как правило, для удобства, я подключаю файл с этим хелпером (и его соседями) в автолоадер composer.

Gravatar image
jumper423
  • Репутация: 58
  • Сниппеты: 9
  • Ревизии: 2

SMS-2-770x285.jpg

yii2-sms

Приём смс сообщений

Ссылка на проект GitHub

Компонент позволяет объединить несколько сервисов по приёму смс сообщений.

Сервисы

На данные момент разработано api для сервисов

Особенности

  • Сразу несколько сервисов по приёму смс сообщений
  • Лёгкая возможность добавить пользовательский сервис
  • Анализ на каком из сервисов есть доступные номера
  • Выбор самого выгодного сервиса для определённого сайта

Продолжение »