Никогда не было и вдруг опять..

Или разгаданная загадка почему не вставляется запись в БД с первой строкой взятой из файла. Долго думал, пока наконец не догадался поглядеть этот файл в HEX редакторе. А там…барабанная дробь..символ BOM от создателей блокнота в Windows 😉

В общем теперь я понял, что при обработке файлов, лучше на всякий случай всегда убирать этот интересный символ, дабы не попасть в просак. Примерно так:

        $dt = trim($dt, "\xEF\xBB\xBF"); 

YII: Шаблон модели при использовании CRUD генератора

Не понятно почему, но CRUD не создает самостоятельно модель для работы с таблицой БД. По крайне мере у меня. Потому из нескольких разрозненных кусков собрал «рыбу»:

<?php

namespace app\models;

class YandexKeys extends \yii\db\ActiveRecord{

public static function tableName(){
    return 'api_map_keys'; // Имя таблицы в БД в которой хранятся записи
}
 
public function rules(){    
        return [
            [['name', 'key'], 'required'],  // какие поля есть в таблице обязательные для добавления
        ];
    }
 
public function attributeLabels(){  // Зададим имена колонок
        return [
            'id' => 'ID',
            'name' => 'Имя',
            'key' => 'Ключ API'
        ];
 }
 
 public static function getAll(){   // выборка всех значений из БД
    $data = self::find()->all();
    return $data;
 }

}

YII2: Bad Request (#400) Unable to verify your data submissionYII2:

Такая ошибка:

Bad Request (#400) Unable to verify your data submissionYII2

может возникнуть при принудительном вызове формы с POST или GET параметрами со страницы сайта. Например:

    <form id="TheFormRoute" method="post" action="?r=site%2Fls_route_report" target="Маршруты доставки">
        <input type="hidden" id="f_count_ls" name="f_count_ls" value="" />
        <input type="hidden" id="f_route_id" name="f_route_id" value="" />
        <input type="hidden" id="f_route_name" name="f_route_name" value="" />
        <input type="hidden" id="f_period" name="f_period" value="" />
        <input type="hidden" id="f_area" name="f_area" value="" />
        <input type="hidden" id="f_area_name" name="f_area_name" value="" />
        <input type="hidden" id="f_ls_list" name="f_ls_list" value="" />
    </form>

В контроллере сайта код вида :

    public function actionLs_route_report()    {
      Yii::$app->controller->enableCsrfValidation = false;
      return $this->render('route_report');  
    }

Как раз и приведет к подобной ошибке:

Это своеобразная защита фреймворка от потенциального флуда. Вариантов решения несколько:

Отключить проверку CSRF глобально:

    'components' => [
        'request' => [
            'enableCsrfValidation' => false,
        ],

Отключить проверку для конкретного контроллера:

class SiteController extends Controller
{
    
    public function beforeAction($action){ 
        $this->enableCsrfValidation = false; 
        return parent::beforeAction($action); 
    }    
    
    public function actionLs_route_report()    {
      return $this->render('route_report');  
    }

Или воспользоваться ПРАВИЛЬНЫМ по мнению фреймворка методом:

    <form id="TheFormRoute" method="post" action="?r=site%2Fls_route_report" target="Маршруты доставки">
        <input id="form-token" type="hidden" name="<?=Yii::$app->request->csrfParam?>" value="<?=Yii::$app->request->csrfToken?>"/>
        <input type="hidden" id="f_count_ls" name="f_count_ls" value="" />
        <input type="hidden" id="f_route_id" name="f_route_id" value="" />
        <input type="hidden" id="f_route_name" name="f_route_name" value="" />
        <input type="hidden" id="f_period" name="f_period" value="" />
        <input type="hidden" id="f_area" name="f_area" value="" />
        <input type="hidden" id="f_area_name" name="f_area_name" value="" />
        <input type="hidden" id="f_ls_list" name="f_ls_list" value="" />
    </form>

YII2: авторизация через Active Directory в шаблоне basic

Почти наверняка это делается легко и не принужденно, каким то другим способом, более стандартным, но: Мы не ищем легких путей (с). Поэтому велосипед.

Для начала в файле params.php добавим настройки необходимые для соединения с AD:

return [
    "ldap_server"=>"wrfwerfwer.ru",    
];

Далее изменим модель User.php следующим образом, добавив в него следующие функции:

 /**
     * Проверяем логин-пароль используя AD. Если в БД такого пользователя нет, до добавляем
     * @param type $username
     * @param type $password
     * @return null/int если авторизация удалась возвращаем id из БД.
     */
    public static function AuthByAD($username,$password){
        $params = require(__DIR__ . '/../config/web.php');
        $value = \yii\helpers\ArrayHelper::getValue($params, 'params');
        
        $ldap_con = ldap_connect($value["ldap_server"]) or die("Could not connect to LDAP server.");
    
        // Устанавливаем флажки протокола
        ldap_set_option($ldap_con, LDAP_OPT_PROTOCOL_VERSION, 3);  // AD Windows 2003 и выше
        ldap_set_option($ldap_con, LDAP_OPT_REFERRALS, 0);         //Определяет, следовать ли автоматически рефералам, возвращенным сервером LDAP. Зачем нужно - не понятно

        // авторизуемся в AD
        $ldapbind = @ldap_bind($ldap_con, $username."@".$value["ldap_server"], $password);                        
        if ($ldapbind==false) return null;

        $user_id=SELF::GetIdByLoginFromDB($username);      
        if ($user_id!=null) return $user_id;
              
        //если такого логина нет в БД, то добавляем..
        $res=(new \yii\db\Query())->createCommand()->insert('users', ['login' =>$username])->execute();
        if ($res==false){
            die("Не удалось вставить запись с новым пользователем..Увы и ах..");
        };        
        $user_id=$this->GetIdByLoginFromDB($username);
        if ($user_id!=null) return $user_id;
        
        return null;
        
    }
         
    /**
     * Получаем id пользователя по логину из БД
     * @param type $login
     * @return type null/id null если пользователь не найден
     */
    public static function GetIdByLoginFromDB($login){
       $rows = (new \yii\db\Query())->select(['id'])->from('users')->where(['login' => $login])->limit(1)->all();
       if (count($rows)>0) {
           return $rows[0]["id"];
       } else {
         return null;  
       };
    }
    
    /**
     * Получить расширенную информацию о пользователе БД по его ID
     * @param type $id
     * @return string
     */
    public static function GetExtendInfoByIdFromDB($id){
       $rows = (new \yii\db\Query())->select(['id',"login","name","photo","email","last_updated"])->from('users')->where(['id' => $id])->limit(1)->all();
       if (count($rows)>0) {
           $rows[0]["username"]=$rows[0]["login"];
           $rows[0]["password"]="";
           $rows[0]["authKey"]=$rows[0]["id"]."key";
           $rows[0]["accessToken"]=$rows[0]["id"]."-token";
           return $rows[0];
       } else {
         return null;  
       };
        
    }

И там-же заменим функцию:

 public static function findIdentity($id)
    {
        $arr=self::GetExtendInfoByIdFromDB($id);        
        $res=new User();
        $res->id=$arr["id"];
        $res->username=$arr["username"];
        $res->password=$arr["password"];
        $res->authKey=$arr["authKey"];
        $res->accessToken=$arr["accessToken"];        
        return $res;      
    }

В модели LoginFrom.php изменим функцию login():

 public function login()
    {
        $user_id=User::AuthByAD(Yii::$app->request->post()["LoginForm"]["username"],Yii::$app->request->post()["LoginForm"]["password"]);        
        if ($user_id==null) {
            $this->addError("password", 'Не верный пароль или логин');
            return false;
        };
        $extended_info=User::GetExtendInfoByIdFromDB($user_id);
        $tuser=new User();
        $tuser->id=$extended_info["id"];
        $tuser->username=$extended_info["username"];
        $tuser->password=$extended_info["password"];
        $tuser->authKey=$extended_info["authKey"];
        $tuser->accessToken=$extended_info["accessToken"];

        return Yii::$app->user->login($tuser, $this->rememberMe ? 3600*24*30 : 0);
        
    }

В результате получим собственно возможность авторизации через AD, с сохранением пользователя AD если такового в БД нет, в базе данных.

PHP: интересное поведение сложения чисел типа float

Был немножно удивлен сегодня, когда пытался сложить два числа типа float обычным оператором +. Например сложение чисел вида 59.86601 + 0,01 успешно выполнялось, а 59.86601+0,001 уже нет. Т.е. результирующее число оставалось прежним. Оказывается для точного сложения чисел, в PHP нужно использовать специальные операторы:

bcadd — Сложить 2 числа произвольной точности
bccomp — Сравнение двух чисел произвольной точности
bcdiv — Операция деления для чисел произвольной точности
bcmod — Получает остаток от деления чисел с произвольной точностью
bcmul — Умножение двух чисел с произвольной точностью
bcpow — Возведение в степень чисел с произвольной точностью
bcpowmod — Возводит одно число в степень другого и возвращает остаток от деления результата на третье число
bcscale — Задает количество чисел после десятичной точки по умолчанию для всех bc math функций.
bcsqrt — Извлекает квадратный корень из числа с произвольной точностью
bcsub — Вычитает одно число с произвольной точностью из другого

Например:

$point2["lat"]=Bcadd($point2["lat"],$cnt,8);
1 3 4 5 6 7 24