Live News for Yii Framework News, fresh extensions and wiki articles about Yii framework. Tue, 29 Sep 2020 23:23:56 +0000 Zend_Feed_Writer 2 (http://framework.zend.com) https://www.yiiframework.com/ [extension] nadar/yii-rest-client Tue, 29 Sep 2020 23:23:39 +0000 https://www.yiiframework.com/extension/nadar/yii-rest-client https://www.yiiframework.com/extension/nadar/yii-rest-client nadario nadario

Yii Framework REST Client

  1. Installation
  2. Usage & Dokumentation

A Yii Framework Component in order to work with the LUYA headless REST Client. The LUYA REST Client is an Active Record similar solution for APIs.

It works with Yii Framework out of the box, therefore those things are included:

  • Use fields in your requests
  • Use sort in your requests
  • Working with expand
  • Caching
  • CRUD (Create new records, Read records, Update records, Delete records) API operations
  • Similar to Yii Framework Active Query and Active Record

TestsMaintainability Test Coverage

Installation

Installation via composer

composer require nadar/yii-rest-client

Usage & Dokumentation

Configure your API Client:

'components' => [
    'api' => [
        'class' => 'Nadar\YiiRestClient\Api',
        'server' => 'https://myapi.com',
        'accessToken' => '...',
    ]
]

To see all LUYA Headless functions see the LUYA Headless Documentation

Now add your Rest Model (Active Endpoint), this represents the Model on the client side. It provides basic getter and setter methods same as Yii Framework.

class ApiCars extends \luya\headless\ActiveEdnpoint
{
    public $id;
    public $name;
    public $year;

    public function getEndpointName()
    {
        return '{{%cars}}';
    }
}

The above example assumes the full endpoint is https://myapi.com/cars.

Working with the Model to make CRUD operations like list, edit, save.

Foreach Data (Will make a GET request):

foreach (ApiCars::find()->all(Yii::$app->api->client)->getModels() as $car) {

    var_dump($car->name);
}

View One and Update the record (Will make a PUT request):

$car = ApiCars::viewOne(1, Yii::$app->api->client);
echo $car->name; // f.e BMW
$car->name = 'Mercedes';
$car->save(Yii::$app->api->client);

Add new entry (will make a POST request):

$car = new ApiCars();
$car->name = 'Honda';
$car->save(Yii::$app->api->client);

To see all LUYA Headless functions see the LUYA Headless Documentation

]]>
0
[extension] yii2-ip2proxy-extension Tue, 22 Sep 2020 06:56:21 +0000 https://www.yiiframework.com/extension/yii2-ip2proxy-extension https://www.yiiframework.com/extension/yii2-ip2proxy-extension hexasoft hexasoft
  1. Requirements
  2. Installation
  3. Usage

Requirements

Yii 2

Installation

php composer.phar require ip2location/ip2proxy-yii

Usage

use IP2ProxyYii\IP2Proxy_Yii;

// (required) Define IP2Proxy database path.
define('IP2PROXY_DATABASE', '/path/to/ip2proxy/database');

// (required) Define IP2Proxy API key.
define('IP2PROXY_API_KEY', 'your_api_key');

// (required) Define IP2Proxy Web service package of different granularity of return information.
define('IP2PROXY_PACKAGE', 'PX1');

// (optional) Define to use https or http.
define('IP2PROXY_USESSL', false);

$IP2Proxy = new IP2Proxy_Yii();

$record = $IP2Proxy->get('1.0.241.135');
echo 'Result from BIN Database:<br>';
echo '<p><strong>IP Address: </strong>' . $record['ipAddress'] . '</p>';
echo '<p><strong>IP Number: </strong>' . $record['ipNumber'] . '</p>';
echo '<p><strong>IP Version: </strong>' . $record['ipVersion'] . '</p>';
echo '<p><strong>Country Code: </strong>' . $record['countryCode'] . '</p>';
echo '<p><strong>Country: </strong>' . $record['countryName'] . '</p>';
echo '<p><strong>State: </strong>' . $record['regionName'] . '</p>';
echo '<p><strong>City: </strong>' . $record['cityName'] . '</p>';
echo '<p><strong>Proxy Type: </strong>' . $record['proxyType'] . '</p>';
echo '<p><strong>Is Proxy: </strong>' . $record['isProxy'] . '</p>';
echo '<p><strong>ISP: </strong>' . $record['isp'] . '</p>';
echo '<p><strong>Domain: </strong>' . $record['domain'] . '</p>';
echo '<p><strong>Usage Type: </strong>' . $record['usageType'] . '</p>';
echo '<p><strong>ASN: </strong>' . $record['asn'] . '</p>';
echo '<p><strong>AS: </strong>' . $record['as'] . '</p>';
echo '<p><strong>Last Seen: </strong>' . $record['lastSeen'] . '</p>';
echo '<p><strong>Threat: </strong>' . $record['threat'] . '</p>';

$record = $IP2Proxy->getWebService('1.0.241.135');
echo 'Result from Web service:<br>';
echo '<pre>';
print_r ($record);
echo '</pre>';
]]>
0
[wiki] Api of Multiple File Uploading in Yii2 Thu, 17 Sep 2020 04:26:34 +0000 https://www.yiiframework.com/wiki/2565/api-of-multiple-file-uploading-in-yii2 https://www.yiiframework.com/wiki/2565/api-of-multiple-file-uploading-in-yii2 fezzymalek fezzymalek

After getting lot's of error and don't know how to perform multiple images api in yii2 finally I get it today

This is my question I asked on forum and it works for me https://forum.yiiframework.com/t/multiple-file-uploading-api-in-yii2/130519

Implement this code in model for Multiple File Uploading

public function rules()
    {
        return [
            [['post_id', 'media'], 'required'],
            [['post_id'], 'integer'],
            [['media'], 'file', 'maxFiles' => 10],//here is my file field
            [['created_at'], 'string', 'max' => 25],
            [['post_id'], 'exist', 'skipOnError' => true, 'targetClass' => Post::className(), 'targetAttribute' => ['post_id' => 'id']],
        ];
    }
    

you can add extension or any skiponempty method also

And this is my controller action where I prformed multiple file uploading

public function actionMultiple(){
        $model = new Media;
        $model->post_id = '2';
        if (Yii::$app->request->ispost) {
            $model->media = UploadedFile::getInstances($model, 'media');
            if ($model->media) {
                foreach ($model->media as $value) {
                    $model = new Media;
                    $model->post_id = '2';
                    $BasePath = Yii::$app->basePath.'/../images/post_images';
                    $filename = time().'-'.$value->baseName.'.'.$value->extension;
                    $model->media = $filename;
                    if ($model->save()) {
                        $value->saveAs($BasePath.$filename);
                    }
                }
                return array('status' => true, 'message' => 'Image Saved'); 
            }
        }
        return array('status' => true, 'data' => $model);
    }

If any query or question I will respond

]]>
0
[wiki] How to email error logs to developer on Yii2 apps Tue, 29 Sep 2020 23:23:40 +0000 https://www.yiiframework.com/wiki/2564/how-to-email-error-logs-to-developer-on-yii2-apps https://www.yiiframework.com/wiki/2564/how-to-email-error-logs-to-developer-on-yii2-apps glpzzz glpzzz

Logging is a very important feature of the application. It let's you know what is happening in every moment. By default, Yii2 basic and advanced application have just a \yii\log\FileTarget target configured.

To receive emails with messages from the app, setup the log component to email (or Telegram, or slack) transport instead (or besides) of file transport:

'components' => [
    // ...
    'log' => [
         'targets' => [
             [
                 'class' => 'yii\log\EmailTarget',
                 'mailer' => 'mailer',
                 'levels' => ['error', 'warning'],
                 'message' => [
                     'from' => ['log@example.com'],
                     'to' => ['developer1@example.com', 'developer2@example.com'],
                     'subject' => 'Log message',
                 ],
             ],
         ],
    ],
    // ...
],

The \yii\log\EmailTarget component is another way to log messages, in this case emailing them via the mailer component of the application as specified on the mailer attribute of EmailTarget configuration. Note that you can also specify messages properties and which levels of messages should be the sent trough this target.

If you want to receive messages via other platforms besides email, there are other components that represents log targets:

Or you can implement your own by subclassing \yii\log\Target

]]>
0
[news] Yii 2.0.38 Mon, 14 Sep 2020 22:26:56 +0000 https://www.yiiframework.com/news/303/yii-2-0-38 https://www.yiiframework.com/news/303/yii-2-0-38 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.38. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Notable changes in this version, additionally to some bug-fixes:

  • PHP 8 support.
  • Latest MySQL and PostgreSQL versions up from 10 are good and added to our test pipelines.
  • Fixtures with circular dependencies are now loaded once instead of throwing exception.
  • Fixed CVE-2020-15148: remote code execution in case application calls unserialize() on user input containing specially crafted string. Details are available in a security advisory.
  • Some Vagrant-related enhancements were added to both basic and advanced application templates.

Thanks to all Yii community members who contribute to the framework, translators who keep documentation translations up to date and community members who answer questions at forums.

There are many active Yii communities so if you need help or want to share your experience, feel free to join them.

A complete list of changes can be found in the CHANGELOG.

]]>
0
[wiki] How to add Schema.org markup to Yii2 pages Fri, 11 Sep 2020 22:09:55 +0000 https://www.yiiframework.com/wiki/2560/how-to-add-schema-org-markup-to-yii2-pages https://www.yiiframework.com/wiki/2560/how-to-add-schema-org-markup-to-yii2-pages glpzzz glpzzz

https://schema.org is a markup system that allows to embed structured data on their web pages for use by search engines and other applications. Let's see how to add Schema.org to our pages on Yii2 based websites using JSON-LD.

Basically what we need is to embed something like this in our pages:

<script type="application/ld+json">
{ 
  "@context": "http://schema.org/",
  "@type": "Movie",
  "name": "Avatar",
  "director": 
    { 
       "@type": "Person",
       "name": "James Cameron",
       "birthDate": "1954-08-16"
    },
  "genre": "Science fiction",
  "trailer": "../movies/avatar-theatrical-trailer.html" 
}
</script>

But we don't like to write scripts like this on Yii2, so let's try to do it in other, more PHP, way.

In the layout we can define some general markup for our website, so we add the following snippet at the beginning of the@app/views/layouts/main.php file:

<?= \yii\helpers\Html::script(isset($this->params['schema'])
    ? $this->params['schema']
    : \yii\helpers\Json::encode([
        '@context' => 'https://schema.org',
        '@type' => 'WebSite',
        'name' => Yii::$app->name,
        'image' => $this->image,
        'url' => Yi::$app->homeUrl,
        'descriptions' => $this->description,
        'author' => [
            '@type' => 'Organization',
            'name' => Yii::$app->name,
            'url' => 'https://www.hogarencuba.com',
            'telephone' => '+5352381595',
        ]
    ]), [
    'type' => 'application/ld+json',
]) ?>

Here we are using the Html::script($content, $options) to include the script with the necessary type option, and Json::encode($value, $options) to generate the JSON. Also we use a page parameter named schema to allow overrides on the markup from other pages. For example, in @app/views/real-estate/view.php we are using:

$this->params['schema'] = \yii\helpers\Json::encode([
    '@context' => 'https://schema.org',
    '@type' => 'Product',
    'name' => $model->title,
    'description' => $model->description,
    'image' => array_map(function ($item) {
        return $item->url;
    }, $model->images),
    'category' => $model->type->description_es,
    'productID' => $model->code,
    'identifier' => $model->code,
    'sku' => $model->code,
    'url' => \yii\helpers\Url::current(),
    'brand' => [
        '@type' => 'Organization',
        'name' => Yii::$app->name,
        'url' => 'https://www.hogarencuba.com',
        'telephone' => '+5352381595',
    ],
    'offers' => [
        '@type' => 'Offer',
        'availability' => 'InStock',
        'url' => \yii\helpers\Url::current(),
        'priceCurrency' => 'CUC',
        'price' => $model->price,
        'priceValidUntil' => date('Y-m-d', strtotime(date("Y-m-d", time()) . " + 365 day")),
        'itemCondition' => 'https://schema.org/UsedCondition',
        'sku' => $model->code,
        'identifier' => $model->code,
        'image' => $model->images[0],
        'category' => $model->type->description_es,
        'offeredBy' => [
            '@type' => 'Organization',
            'name' => Yii::$app->name,
            'url' => 'https://www.hogarencuba.com',
            'telephone' => '+5352381595',
        ]
    ]
]);

Here we redefine the schema for this page with more complex markup: a product with an offer.

This way all the pages on our website will have a schema.org markup defined: in the layout we have a default and in other pages we can redefine setting the value on $this->params['schema'].

]]>
0
[wiki] How to add Open Graph and Twitter Card tags to Yii2 website. Tue, 29 Sep 2020 23:23:40 +0000 https://www.yiiframework.com/wiki/2559/how-to-add-open-graph-and-twitter-card-tags-to-yii2-website https://www.yiiframework.com/wiki/2559/how-to-add-open-graph-and-twitter-card-tags-to-yii2-website glpzzz glpzzz

OpenGraph and Twitter Cards are two metadata sets that allow to describe web pages and make it more understandable for Facebook and Twitter respectively.

There a lot of meta tags to add to a simple webpage, so let's use TaggedView

This component overrides the yii\web\View adding more attributes to it, allowing to set the values on every view. Usually we setup page title with

$this->title = $model->title;

Now, with TaggedView we are able to set:

$this->title = $model->title;
$this->description = $model->abstract;
$this->image = $model->image;
$this->keywords = ['foo', 'bar'];

And this will generate the proper OpenGraph, Twitter Card and HTML meta description tags for this page.

Also, we can define default values for every tag in the component configuration that will be available for every page and just will be overriden if redefined as in previous example.

'components' => [
    //...
    'view' => [
        'class' => 'daxslab\taggedview\View',
        'site_name' => '',
        'author' => '',
        'locale' => '',
        'generator' => '',
        'updated_time' => '',
    ],
    //...
]

Some of this properties have default values assigned, like site_name that gets Yii::$app->name by default.

Result of usage on a website:

<title>¿Deseas comprar o vender una casa en Cuba? | HogarEnCuba, para comprar y vender casas en Cuba</title>
<meta name="author" content="Daxslab (https://www.daxslab.com)">
<meta name="description" content="Hay 580 casas...">
<meta name="generator" content="Yii2 PHP Framework (http://www.yiiframework.com)">
<meta name="keywords" content="HogarEnCuba, ...">
<meta name="robots" content="follow">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="Hay 580 casas...">
<meta name="twitter:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta name="twitter:site" content="HogarEnCuba">
<meta name="twitter:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta name="twitter:type" content="website">
<meta name="twitter:url" content="https://www.hogarencuba.com/">
<meta property="og:description" content="Hay 580 casas...">
<meta property="og:image" content="https://www.hogarencuba.com/images/main-identifier_es.png">
<meta property="og:locale" content="es">
<meta property="og:site_name" content="HogarEnCuba">
<meta property="og:title" content="¿Deseas comprar o vender una casa en Cuba?">
<meta property="og:type" content="website">
<meta property="og:updated_time" content="10 sept. 2020 9:43:00">
]]>
0
[extension] shahimian/yii2-aceydu Tue, 29 Sep 2020 23:23:40 +0000 https://www.yiiframework.com/extension/shahimian/yii2-aceydu https://www.yiiframework.com/extension/shahimian/yii2-aceydu shahimian shahimian

Acey Ducey

This is a simulation of the Acey Ducey card game. In the game, the dealer (the computer) deals two cards face up. You have an option to bet or not to bet depending on whether or not you feel the next card dealt will have a value between the first two. Your initial money is set to $100; you may alter Statement 170 if you want to start with more or less than $100. The game keeps going on until you lose all your money.

Setup

Enter composer shahimian\yii2-aceydu and installing it and able to be run this module with this expression in web.php and that's it. enjoy it!

$config = [
    'components' => [
    'modules' => [   
        'aceydu' => [
              'class' => 'shahimian\aceydu\Module',
              'layout' => 'main',
        ],
     ],
];

Notice: This introduction of description is from 101 BASIC Computer Games book at 1975 by Digital Equipment Corporation.

]]>
0
[extension] deadmantfa/yii2-zoho-api-client Tue, 29 Sep 2020 23:23:40 +0000 https://www.yiiframework.com/extension/deadmantfa/yii2-zoho-api-client https://www.yiiframework.com/extension/deadmantfa/yii2-zoho-api-client deadmantfa deadmantfa

Yii2 Zoho REST API Client

  1. Installation
  2. Configuration
  3. Usage
  4. Dependencies

A Client for Zoho RESTful API's

Powered By Yii2 Type

Packagist VersionGitHubPackagist StarsGitHub top languagePackagist DownloadsGitHub starsGitHub last commitMaintenance saythanks

forthebadge

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist deadmantfa/yii2-zoho-api-client "^v1.0.x"

or add

"deadmantfa/yii2-zoho-api-client": "^v1.0.x"

to the require section of your composer.json file.

Configuration

Once the extension is installed, add it to your configuration :

Please note the redirect url for OAUTH2 to work properly, please add it to your client on Zoho before continuing further. Replace `<YOUR_DOMAIN_NAME>` with an actual domain or localhost

<YOUR_DOMAIN_NAME>/zoho/default/auth?authclient=zoho

In your params.php or params-local.php define the following `php

'zoho-api-client-id' => '<ZOHO_API_CLIENT_ID>',
'zoho-api-client-secret' => '<ZOHO_API_CLIENT_SECRET>',
'zoho-organization-id' => '<ZOHO_ORGANIZATION_ID>',
'zoho-redirect-uri' => '<YOUR_DOMAIN_NAME>/zoho/default/auth?authclient=zoho'

Add the following in one of these file

`app/common/config/main.php` or `app/backend/config/main.php`
```php
    'modules' => [
        'zoho' => [
            'class' => \deadmantfa\yii2\zoho\Module::class,
        ],
    ],
    'components' => [
        'authClientCollection' => [
            'class' => \yii\authclient\Collection::class,
            'clients' => [
                'zoho' => [
                    'class' => \deadmantfa\yii2\zoho\auth\ZohoAuthClient::class,
                    'clientId' => $params['zoho-api-client-id'],
                    'clientSecret' => $params['zoho-api-client-secret'],
                    'returnUrl' => $params['zoho-redirect-uri'],
                ],
            ],
        ],
        'zoho'=> [
            'class' => \deadmantfa\yii2\zoho\components\ZohoApiClient::class,
            'apiBaseUrl'=>'https://inventory.zoho.com/api/v1/',
            'organizationId'=>$params['zoho-organization-id']
        ]
    ];  

For Migration add the following to

app/console/config/main.php


use yii\console\controllers\MigrateController;

    'controllerMap' => [
        'migrate' => [
            'class' => MigrateController::class,
            'migrationPath' => [
                '@app/migrations',
                '@yii/rbac/migrations', // Just in case you forgot to run it on console (see next note)
            ],
            'migrationNamespaces' => [
                'deadmantfa\yii2\zoho\migrations',
            ],
        ],
    ],

Then run the following command

./yii migrate

Usage

To use the zoho api we need to first authorize and generate access_token and refresh_token

(http://<YOUR_DOMAIN_NAME>/zoho)

Follow the screenshots below

Step 1

Step 2

Step 3

After generating the access token and refresh token you can now call any api which is provided by Zoho

Yii::$app->zoho->get('items', []);
Yii::$app->zoho->post('items', []);
Yii::$app->zoho->put('items', []);
Yii::$app->zoho->delete('items', []);

Zoho Rest API Documentation

Dependencies

]]>
0
[extension] becksonq/yii2-widget-breadcrumbs-microdata Tue, 29 Sep 2020 23:23:40 +0000 https://www.yiiframework.com/extension/becksonq/yii2-widget-breadcrumbs-microdata https://www.yiiframework.com/extension/becksonq/yii2-widget-breadcrumbs-microdata becksonq becksonq

Yii2 bootstrap4 breadcrumbs widget

  1. Installation
  2. Usage

Yii2 bootstrap4 breadcrumbs widget with microdata markup from Schema.org

Packagist Packagist license

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist becksonq/yii2-widget-breadcrumbs-microdata "*"

or add

"becksonq/yii2-widget-breadcrumbs-microdata": "*"

to the require section of your composer.json file.

Usage

Once the extension is installed, simply use it in your code by :

<?= \becksonq\breadcrumbs\Breadcrumbs::widget(); ?>
]]>
0
[news] HTTP 1.0.0 released Tue, 01 Sep 2020 14:17:25 +0000 https://www.yiiframework.com/news/302/http-1-0-0-released https://www.yiiframework.com/news/302/http-1-0-0-released samdark samdark

First verison of HTTP independent package was released.

The package provides:

  • Constants for HTTP protocol headers, methods and statuses. All along with short descriptions and RFC links.
  • PSR-7, PSR-17 PhpStorm meta for HTTP protocol headers, methods and statuses.

More details could be found in the package README.

]]>
0
[news] Strings 1.0.0 released Mon, 31 Aug 2020 21:23:34 +0000 https://www.yiiframework.com/news/301/strings-1-0-0-released https://www.yiiframework.com/news/301/strings-1-0-0-released samdark samdark

First version of yiisoft/strings standalone package was released.

The package provides:

  • StringHelper that has static methods to work with strings;
  • NumericHelper that has static methods to work with numeric strings;
  • Inflector provides methods such as toPlural() or toSlug() that derive a new string based on the string given;
  • WildcardPattern is a shell wildcard pattern to match strings against.

More details could be found in the package README.

]]>
0
[extension] daxslab/yii2-advanced-website Tue, 29 Sep 2020 23:23:40 +0000 https://www.yiiframework.com/extension/daxslab/yii2-advanced-website https://www.yiiframework.com/extension/daxslab/yii2-advanced-website glpzzz glpzzz

Yii2 Advanced Website

  1. INSTALLATION
  2. DIRECTORY STRUCTURE

Fork from yiisoft/yii2-app-advanced ready to create a website based on daxslab/yii2-website-module

INSTALLATION

composer install

./init

//setup database in common/config/main-local.php

./yii migrate

./yii website/create <domainname>

//set the resulting token in website module configuration in common/config/main-local.php

DIRECTORY STRUCTURE

common
    config/              contains shared configurations
    mail/                contains view files for e-mails
    models/              contains model classes used in both backend and frontend
    tests/               contains tests for common classes    
console
    config/              contains console configurations
    controllers/         contains console controllers (commands)
    migrations/          contains database migrations
    models/              contains console-specific model classes
    runtime/             contains files generated during runtime
backend
    assets/              contains application assets such as JavaScript and CSS
    config/              contains backend configurations
    controllers/         contains Web controller classes
    models/              contains backend-specific model classes
    runtime/             contains files generated during runtime
    tests/               contains tests for backend application    
    views/               contains view files for the Web application
    web/                 contains the entry script and Web resources
frontend
    assets/              contains application assets such as JavaScript and CSS
    config/              contains frontend configurations
    controllers/         contains Web controller classes
    models/              contains frontend-specific model classes
    runtime/             contains files generated during runtime
    tests/               contains tests for frontend application
    views/               contains view files for the Web application
    web/                 contains the entry script and Web resources
    widgets/             contains frontend widgets
vendor/                  contains dependent 3rd-party packages
environments/            contains environment-based overrides
]]>
0
[extension] daxslab/yii2-cas-client Tue, 29 Sep 2020 23:23:41 +0000 https://www.yiiframework.com/extension/daxslab/yii2-cas-client https://www.yiiframework.com/extension/daxslab/yii2-cas-client glpzzz glpzzz

yii2-auth-cas

  1. Usage
  2. Notes

Yii2 library for authentication and user local record update by CAS, using the library phpCAS.

Usage

  1. Add this to the project with composer require daxslab/yii2casclient

  2. Configure the Yii2 application, e.g. in backend/config/main.php :

    ` return [

     ...
     'session' => [
         'class' => 'poofe\yii2casclient\cas\Session',
         ...
     ],
     'modules' => [
         'cas' => [
             'class' => \daxslab\yii2casclient\cas\CasModule::class,
             'profileClass' => \common\models\Profile::class,
             'userClass' => \common\models\User::class,
             'config' => [
             'host' => '192.168.0.113',
             'port' => '8000',
             'path' => 'rrhh/default/user/cas',
             // optional parameters
             'certfile' => false, // empty, or path to a SSL cert, or false to ignore certs
             'debug' => true, // will add many logs into X/runtime/logs/cas.log
             ],
         ],
    

    `

  3. Add actions that use this CAS module, e.g. in SiteController :

    ` public function actionLogin() {

     if (!Yii::$app->user->isGuest) {
         return $this->goHome();
     }
     return $this->redirect(['/cas/auth/login']);
    

    }

    public function actionLogout() {

     if (Yii::$app->user->isGuest) {
         return $this->redirect(['/cas/auth/logout']);
     }
     return $this->goHome();
    

    } `

Notes

The user component that implements yii\web\IdentityInterface will be used to fetch the local profile after querying the CAS server. It means that if User is the App component and CAS returns a username of "bibendum", the authentication will be successful if and only if the result of User::findIdentity("bibendum") is not null.

The action path '/cas/auth/login' starts with the alias of the module, as defined in the application configuration, e.g. 'cas' in 'modules' => [ 'cas' => [ ... ] ].

The profileClass and userClass config parameters are used to update the profile and user table. The update proccess is based on the common attributes between the CAS server and the local tables profile and user.

]]>
0
[extension] daxslab/yii2-sharelinks-widget Tue, 29 Sep 2020 23:23:41 +0000 https://www.yiiframework.com/extension/daxslab/yii2-sharelinks-widget https://www.yiiframework.com/extension/daxslab/yii2-sharelinks-widget glpzzz glpzzz

yii2-sharelinks-widget

  1. Supported Social Networks
  2. Installation via Composer
  3. Usage Example
  4. Example custom view file

Yii2 widget for popular social networks "share link" generation. Based on https://github.com/ijackua/yii2-sharelinks-widget.

Supported Social Networks

Installation via Composer

add to require section of your composer.json "daxslab/yii2-sharelinks-widget": "*" and run composer update

Usage Example

in template file use ~~~php <?php echo \daxslab\sharelinks\ShareLinks::widget(

[
	'viewName' => '@app/views/mypath/shareLinks.php'   //custom view file for you links appearance
]

); ?> ~~~

Example custom view file

This eaxmple uses custom icons from Fontello, but you can make it what ever you want and customize what links do you need and what not. Your @app/views/mypath/shareLinks.php file could look like ~~~php <?php use daxslab\sharelinks\ShareLinks; use \yii\helpers\Html;

/**

  • @var yii\base\View $this */

?>

<?= Html::a('', $this->context->shareUrl(ShareLinks::SOCIAL_FACEBOOK), ['title' => 'Share to Facebook']) ?> <?= Html::a('', $this->context->shareUrl(ShareLinks::SOCIAL_TWITTER), ['title' => 'Share to Twitter']) ?> <?= Html::a('', $this->context->shareUrl(ShareLinks::SOCIAL_LINKEDIN), ['title' => 'Share to LinkedIn']) ?> <?= Html::a('', $this->context->shareUrl(ShareLinks::SOCIAL_GPLUS), ['title' => 'Share to Google Plus']) ?> <?= Html::a('', $this->context->shareUrl(ShareLinks::SOCIAL_VKONTAKTE), ['title' => 'Share to Vkontakte']) ?> <?= Html::a('', $this->context->shareUrl(ShareLinks::SOCIAL_KINDLE), ['title' => 'Send to Kindle']) ?>

]]>
0
[extension] daxslab/yii2-simple-line-icons Tue, 29 Sep 2020 23:23:41 +0000 https://www.yiiframework.com/extension/daxslab/yii2-simple-line-icons https://www.yiiframework.com/extension/daxslab/yii2-simple-line-icons glpzzz glpzzz

Simple Line Icons Asset Bundle for Yii2

  1. Installation
  2. Usage

Installation

The preferred way to install this extension is through composer.

Add to the require section of your composer.json file:

"daxslab/yii2-simple-line-icons": "~1.0"

And run in terminal

composer update

Usage

Use as the asset bundle in Yii2 View files.

// write this in your view file or layout
\daxslab\assets\SimpleLineIconsAsset::register($this);

Or maybe you prefer to include it in your asset dependency.

public $depends = [
  // ...
  'daxslab\assets\SimpleLineIconsAsset',
  // ...
];
]]>
0
[extension] daxslab/yii2-coreui Tue, 29 Sep 2020 23:23:41 +0000 https://www.yiiframework.com/extension/daxslab/yii2-coreui https://www.yiiframework.com/extension/daxslab/yii2-coreui glpzzz glpzzz

Yii2 CoreUI

  1. Installation
  2. Usage

Latest Stable Version Total Downloads Latest Unstable Version License

Yii2 extension includind the base layout for CoreUI admin dashboard

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist daxslab/yii2-coreui "*"

or add

"daxslab/yii2-coreui": "*"

to the require section of your composer.json file.

Usage

Make your module or controller layout to be @daxslab/coreui/views/layouts/main.php by modifiying the layout attribute.

Also you can make your @app/views/layouts/main.php to look like:


<?php $this->beginContent('@daxslab/coreui/views/layouts/main.php') ?>
    <?= $content ?>
<?php $this->endContent() ?>

Consider that there a a group of variables at the beginning of @daxslab/coreui/views/layouts/main.php that you can set to specify brand icons, menu contents, etc.

]]>
0
[extension] daxslab/yii2-website-module Tue, 29 Sep 2020 23:23:41 +0000 https://www.yiiframework.com/extension/daxslab/yii2-website-module https://www.yiiframework.com/extension/daxslab/yii2-website-module glpzzz glpzzz

Yii2 Website Module

  1. Installation
  2. Introduction
  3. Configuration
  4. Usage

Latest Stable Version Total Downloads Latest Unstable Version License

Yii2 module to implement a website.

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist daxslab/yii2-website-module "*"

or add

"daxslab/yii2-website-module": "*"

to the require section of your composer.json file.

Introduction

Website tries to be an unobstrusive CMS without limiting the capabilities of Yii2 framework as development platform. The idea is that you can website features to an existing application, or just created a website based on Yii2 framework.

The idea behind Website module is a bit different compared with other CMS. While generally pages, posts and categories are managed, in Website everything is a page and every page can have children pages so,

  • a page without subpages can be considered a regular page
  • a page with subpages can be considered a category
  • a subpage can be considered a post

The resulting tree can then have wathever depth is required.

Besides pages, Website also manages Media: any attached file that can be referenced in the resulting website. The module handles the uploading process.

Also you can manage Menus with Website module. For every menu you can create menu items and this than be pointed to any URL. When creating a menu item you either type the label and URL, or select from existing pages.

Configuration

Website is meant to be used with the Yii2 Advanced Application template. Some modification could be done to make it usable with basic template.

Module

First configure the module for all the apps in common/config/main.php

//...
'modules' => [
	'website' => [
		'class' => daxslab\website\Module::class,
		'languages' => ['en', 'es', 'it'],
		'token' => 'some-string-here'
	]
]
//...

Here notice the specified attributes:

  • languages: array with the languages that will be active for creating content
  • token: string identifying every website in case that several are used.
Database

It is assumed that you are using some database and that the connection to it it's already set. Configure migrations in console/config/main.php

//...
'controllerMap' => [
    'migrate' => [
        'class' => 'yii\console\controllers\MigrateController',
        'migrationNamespaces' => [
            'daxslab\website\migrations',
        ]
    ],
],
//...
Controllers

Configure controllers namespaces for the module in each app. Let's start with frontend/config/main.php

//...
'modules' => [
	'website' => [
		'controllerNamespace' => 'website\daxslab\controllers\frontend'
	]
]
//...

And similar for backend/config/main.php

//...
'modules' => [
	'website' => [
		'controllerNamespace' => 'website\daxslab\controllers\backend'
	]

]
//...

Usage

The module provides two sets of controllers: frontend and backend.

]]>
0
[extension] vatandoost/filemanager Tue, 29 Sep 2020 23:23:41 +0000 https://www.yiiframework.com/extension/vatandoost/filemanager https://www.yiiframework.com/extension/vatandoost/filemanager vatandoost vatandoost

Yii2 FileManager

  1. features:
  2. Screenshots
  3. Installation
  4. How it works

this module is an easy way to save and manage your files in yii2

features:

  • manage files in two main category :
    • private
    • public
  • multiple handlers :
    • ftp or sftp
    • local
  • manger classes to manage any file type
  • selector widget for select files from filemanager
  • filetrait for models class to easily manage and save files
  • filemanager UI:
    • preview files
      • google docs viewer
      • lightbox image viewer
      • pdf viewer
      • jplayer video and audio preview
      • local office viewer by using unoconv
    • rename files
    • delete files
    • search and sort files
  • upload files from another url
  • resize image
  • upload files using blueimp jquery file upload
  • FileHelper class to manage files in your app
  • get files query in your app to merge with other queries by selecting file addresses
  • use bootstrap4 theme
  • multi language

Screenshots

<img width="250px" src="http://vatandoost.com/assets/filemanager/1.png" alt="yii2 filemanager ui"/>

<img width="250px" src="http://vatandoost.com/assets/filemanager/2.png" alt="yii2 filemanager upload"/>

<img width="250px" src="http://vatandoost.com/assets/filemanager/3.png" alt="yii2 filemanager selector"/>

<img width="250px" src="http://vatandoost.com/assets/filemanager/4.png" alt="yii2 filemanager preview box"/>

Installation

use composer composer require vatandoost/filemanager

Apply migration yii migrate --migrationPath=vendor/vatandoost/filemanager/migrations

Configuration

In your configuration file add filemanager module in modules and also add filemanager module to your bootstrap components ` 'modules' => [

'filemanager' => [
    'class' => 'vatandoost\filemanager\Module',
    'publicFiles' => '@frontend/web/uploads',
    'privateFiles' => '@frontend/uploads',
]

], 'bootstrap' => ['filemanager'] `

available configurations :

'filemanager' => [
    'class' => 'vatandoost\filemanager\Module',
    'title' => 'Yii2 filemanager',
    'publicFiles' => '@frontend/web/uploads',
    'privateFiles' => '@frontend/uploads',
    'publicFilesUrl' => '/uploads',
    'params' => [
        'upload_files' => true,
        'url_upload' => true,
        'default_handler' => FileType::HANDLER_TYPE_LOCAL,

        'jplayer_exts' => array("mp4", "flv", "webmv", "webma", "webm", "m4a", "m4v", "ogv", "oga", "mp3", "midi", "mid", "ogg", "wav"),
        'cad_exts' => array('dwg', 'dxf', 'hpgl', 'plt', 'spl', 'step', 'stp', 'iges', 'igs', 'sat', 'cgm', 'svg'),

        // Preview with Google Documents
        'googledoc_enabled' => true,
        'googledoc_file_exts' => array('doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'odt', 'odp', 'ods'),


        'image_extensions' => ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'ico'],
        'file_extensions' => ['doc', 'docx', 'rtf', 'pdf', 'xls', 'xlsx', 'txt', 'csv', 'html', 'xhtml', 'psd', 'sql', 'log', 'fla', 'xml', 'ade', 'adp', 'mdb', 'accdb', 'ppt', 'pptx', 'odt', 'ots', 'ott', 'odb', 'odg', 'otp', 'otg', 'odf', 'ods', 'odp', 'css', 'ai', 'kmz', 'dwg', 'dxf', 'hpgl', 'plt', 'spl', 'step', 'stp', 'iges', 'igs', 'sat', 'cgm', 'tiff', ''], //Files
        'video_extensions' => ['mov', 'mpeg', 'm4v', 'mp4', 'avi', 'mpg', 'wma', "flv", "webm"],
        'audio_extensions' => ['mp3', 'mpga', 'm4a', 'ac3', 'aiff', 'mid', 'ogg', 'wav'],
        'misc_extensions' => ['zip', 'rar', 'gz', 'tar', 'iso', 'dmg'],

        //size of image boxes in filemanager ui
        'image_preview_size' => [
            'w' => 120,
            'h' => 100,
        ],
        'rename_files' => true,
        'delete_files' => true,
        // ftp handler options
        'ftp' => [
            'ftp_host' => "example.com", //put the FTP host
            'ftp_user' => "example",
            'ftp_pass' => "*****",
            'ftp_public_folder' => "/public_html/filemanager",
            'ftp_private_folder' => "/filemanager/uploads",
            'ftp_base_url' => "https://example.com/filemanager",
            'ftp_ssl' => false,
            'ftp_port' => 21,
        ],

        // convert office files to pdf format
        'convert_pdf_preview' => false,
        'unoconvBaseUrl' => 'localhost',
    ],
    'superAdminRole' => 'filemanager_admin',
]

How it works

there are two table to save files informations

  • table file_type
  • table files

table file_type: it maintain information about files in a type . to use filemanager you have to create a file_type row at first.

if you don't define that when file saved in model it will be create.

each file type have a manager class to manage their files ( Yii2 model classes )

fileType fields: `php

'file_type_id' => 'File Type ID', // integer autoincrement  
'name' => 'Name', // unique name
'title' => 'Title', // title to show in filemanager ui
'is_public' => 'Is Public', // (boolean) show files are public or private 
'mime_types' => 'Mime Types',
'max_size' => 'Max Size', // maximum file size to upload
'extensions' => 'Extensions', // like  "img,png,jpg"
'files_path' => 'Files Path', // directory path to save files
'manager_class' => 'manager class', // class to manage files if empty it will use \vatandoost\filemanager\libs\BaseManager  
'handler_type' => 'type of handler class', // 1 for local handler 2 for Ftp Handler
'has_public_thumb' => 'public thumbnail', // if files are private they can have public thumbnail for performance
'has_force_relation_id' => 'force to has relation id to work with filemanager',
 
manager class always is a model that it can change some of behaviors of filemanager.

you can check bellow class to see available options and you can define any of them to your model

\vatandoost\filemanager\libs\BaseManager
 * filterQueryFiles($query, $fileType, $relation_id = null)
 * canView($file)
 * canDelete($file)
 * beforeFileDelete($file) 
 * afterFileDelete($file)
 * fileTypeConfig($fileType, $relationId = null)


filemanager ui is an iframe that show files to select or manage.
 you can use it as an standalone page or you can add it to your page

options to open filemanager : "filemanager/dialog/index"
  * unique_name : its combine of file_type_name and relation id that joined together with ','
  * selector: if you send selector=off , it disable selecting files only show them
  * callback: function name from parent window to call after select
  * field_id: input id to put file Ids into its value
  * multiple: (0/1) it shows can select multiple files or not ( default : 1)
for example : 

http://localhost/filemanager/frontend/web/filemanager/dialog/index?callback=filemanager_selector_callback&unique_name=frontend_models_testfile,36&multiple=1&field_id=testfile-files_str
  
usage
---

you can use this module without UI and selector,
 It can help you to upload files and access them easily
 
 you need to add fileTrait to your model and then define your attributes
 
 example model (manager class)
 
 ```php
class TestFile extends \yii\db\ActiveRecord
{

    use \vatandoost\filemanager\libs\FileTrait;

    public $file;
    
    public static function tableName()
    {
        return 'test_file';
    }

    public function rules()
    {
        return [
            [
                ['file'],
                'vatandoost\filemanager\libs\FileValidator', // you have to use this file validator or extend of that
                'file_type_name' => '', // if you did not define this it will complete based on class name  
                'relation_field' => 'id', // use this attribute to save in relation field in files table 
                //'target_field' => 'file_id', // file id will save in this attribute after save if multiple false
                'thumb' => [250, 250], // thumbnail size if files are image
                'multiple' => true // we can add multiple files for this field
                // you can define any other \yii\validators\FileValidator properties here
            ],
            // other attributes
            //[['file_id'], 'integer'],
            [['name'], 'string', 'max' => 255],
            [['name'], 'required'],
            [['files_str'], 'string', 'max' => 255],
        ];
    }

    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'name' => 'Name',
            //'file_id' => 'File ID',
            'files_str' => 'File list',
        ];
    }
}

for upload files in yii2 you just need to run this function `php $model->saveFiles() `

example use of file upload in controller `php public function actionCreate() {

$model = new TestFile();

if ($model->load(Yii::$app->request->post()) && $model->save() && $model->saveFiles()) {
    return $this->redirect(['view', 'id' => $model->id]);
}

return $this->render('create', [
    'model' => $model,
]);

} `

example use of file upload input:

echo $form->field($model, 'file')->fileInput();

example use of file selector: `php echo $form->field($model, 'files_str')->widget(\vatandoost\filemanager\widgets\selector\Selector::class, [

'fileTypeName' => 'frontend_models_testfile',
'relationId' => $model->id,
'multiple' => true

]); `

there are some other functions to use :

  • $model->getFiles($attribute, $onlyModels = false) // to get all files from model
  • $model->filesQuery($file_type_name = null) // to get ActiveQuery class to merge with other tables

there is a helper class too. you can use that's static functions to work with your files across your app:

\vatandoost\filemanager\libs\FileHelper

  • FileHelper::getFileUrl($file_id, $thumb = false)
  • FileHelper::getFileInfo($file_id)
  • FileHelper::getFileInfoByModel(File $file)
  • FileHelper::deleteFile($file_id, $checkPermissions = true)
  • FileHelper::renameFile($file_id, $new_name)
  • FileHelper::deleteFileByModel(File $model, $checkPermissions = true)
  • FileHelper::getFileContent($id = null, $model = null)
  • FileHelper::getLocalFilePath($file, $fileType = null)

I'll put complete documentation asap.

If you need any help just email me. masoudvatandoost1@gmail.com

]]>
0
[news] Injector 1.0.2 released Tue, 29 Sep 2020 23:23:44 +0000 https://www.yiiframework.com/news/300/injector-1-0-2-released https://www.yiiframework.com/news/300/injector-1-0-2-released samdark samdark

New version of Injector package was tagged. It is focused on PHP 8 support and takes new PHP features, such as union types, into account.

Check it out

]]>
0
[wiki] Yii v2 snippet guide II Wed, 09 Sep 2020 09:51:40 +0000 https://www.yiiframework.com/wiki/2558/yii-v2-snippet-guide-ii https://www.yiiframework.com/wiki/2558/yii-v2-snippet-guide-ii rackycz rackycz
  1. Intro
  2. Connection to MSSQL
  3. Using MSSQL database as the 2nd DB in the Yii2 project
  4. Creating models in Gii for remote MSSQL tables
  5. PhpExcel/PhpSpreadsheet in Yii 2 and sending binary content to the browser

Intro

Hi. I had to split my article as max length was reached. Check my previous articles here (mainly the 1st one):

Connection to MSSQL

You will need MSSQL drivers in PHP. Programatically you can list them or test their presence like this:

var_dump(\PDO::getAvailableDrivers());

if (in_array('sqlsrv', \PDO::getAvailableDrivers())) {
  // ... MsSQL driver is available, do something
}

Based on your system you have to download different driver. The differences are x64 vs x86 and ThreadSafe vs nonThreadSafe. In Windows I always use ThreadSafe. Explanation.

Newest PHP drivers are here.

  • Drivers v5.8 = PHP 7.2 - 7.4

Older PHP drivers here.

  • Drivers v4.0 = PHP 7.0 - 7.1
  • Drivers v3.2 = PHP 5.x

Once drivers are downloaded and extracted, pick one DLL file and place it into folder "php/ext". On Windows it might be for example here: "C:\xampp\php\ext"

Note: In some situations you could also need these OBDC drivers, but I am not sure when:

Now file php.ini must be modified. On Windows it might be placed here: "C:\xampp\php\php.ini". Open it and search for rows starting with word "extension" and paste there cca this:

extension={filename.dll}
// Example:
extension=php_pdo_sqlsrv_74_ts_x64.dll

Now restart Apache and visit phpinfo() web page. You should see section "pdo_sqlsrv". If you are using XAMPP, it might be on this URL: http://localhost/dashboard/phpinfo.php.

Then just add connection to your MSSQL DB in Yii2 config. In my case the database was remote so I needed to create 2nd DB connection. Read next chapter how to do it.

Using MSSQL database as the 2nd DB in the Yii2 project

Adding 2nd database is done like this in yii-config:

'db' => $db, // the original DB
'db2'=>[
  'class' => 'yii\db\Connection',
  'driverName' => 'sqlsrv',
  // I was not able to specify database like this: 
  // 'dsn' => 'sqlsrv:Server={serverName};Database={dbName}',
  'dsn' => 'sqlsrv:Server={serverName}', 
  'username' => '{username}',
  'password' => '{pwd}',
  'charset' => 'utf8',
],

That's it. Now you can test your DB like this:

$result = Yii::$app->db2->createCommand('SELECT * FROM {tblname}')->queryAll();
var_dump($result);

Note that in MSSQL you can have longer table names. Example: CATEGORY.SCHEMA.TBL_NAME

And your first test-model can look like this (file MyMsModel.php):

namespace app\models;
use Yii;
use yii\helpers\ArrayHelper;
class MyMsModel extends \yii\db\ActiveRecord
{
  public static function getDb()
  {
    return \Yii::$app->db2; // or Yii::$app->get('db2');
  }
  public static function tableName()
  {
    return 'CATEGORY.SCHEMA.TBL_NAME'; // or SCHEMA.TBL_NAME
  }
}

Usage:

$result = MyMsModel::find()->limit(2)->all();
var_dump($result);

Creating models in Gii for remote MSSQL tables

Once you have added the 2nd database (read above) go to the Model Generator in Gii. Change there the DB connection to whatever you named the connection in yii-config (in the example above it was "db2") and set tablename in format: SCHEMA.TBL_NAME. If MSSQL server has more databases, one of them is set to be the main DB. This will be used I think. I haven't succeeded to change the DB. DB can be set in the DSN string, but it had no effect in my case.

PhpExcel/PhpSpreadsheet in Yii 2 and sending binary content to the browser

In previous chapters I showed how to use PhpExcel in Yii 1. Now I needed it also in Yii 2 and it was extremely easy.

Note: PhpExcel is deprecated and was replaced with PhpSpreadsheet.

// 1) Command line:
// This downloads everything to folder "vendor"
composer require phpoffice/phpspreadsheet --prefer-source
// --prefer-source ... also documentation and samples are downloaded 
// ... adds cca 40MB and 1400 files 
// ... only for devel system

// 2) PHP:
// Now you can directly use the package without any configuration:
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();

// Uncomment following rows if you want to set col width:
//$sheet->getColumnDimension('A')->setAutoSize(false);
//$sheet->getColumnDimension('A')->setWidth("50");

$sheet->setCellValue('A1', 'Hello World !');

$writer = new Xlsx($spreadsheet);

// You can save the file on the server:
// $writer->save('hello_world.xlsx'); 

// Or you can send the file directly to the browser so user can download it:
// header('Content-Type: application/vnd.ms-excel'); // This is probably for older XLS files.
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // This is for XLSX files (they are basically zip files).
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
$writer->save('php://output');
exit();

Thanks to DbCreator for the idea how to send XLSX to browser. Nevertheless exit() or die() should not be called. Read the link.

Following is my idea which originates from method renderPhpFile() from Yii2:

ob_start();
ob_implicit_flush(false);
$writer->save('php://output');
$file = ob_get_clean();

return \Yii::$app->response->sendContentAsFile($file, 'file.xlsx',[
  'mimeType' => 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'inline' => false
]);

This also worked for me:

$tmpFileName = uniqid('file_').'.xlsx';
$writer->save($tmpFileName);    
header('Content-Type: application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 
header('Content-Disposition: attachment;filename="filename.xlsx"');
header('Cache-Control: max-age=0');
echo file_get_contents($tmpFileName);
unlink($tmpFileName);
exit();

Note: But exit() or die() should not be called. Read the "DbCreator" link above.

]]>
0
[news] Json 1.0.0 released Tue, 29 Sep 2020 23:23:44 +0000 https://www.yiiframework.com/news/299/json-1-0-0-released https://www.yiiframework.com/news/299/json-1-0-0-released samdark samdark

First version of yiisoft/json standalone package was released.

The package provides methods to encode and decode JSON.

  • It always throws \JsonException instead of returning false on error.
  • It has sensible defaults, so you don't have to specify flags all the time.
  • It has handy method to encode for HTML safely.
  • It handles \JsonSerializable, \DateTimeInterface, and \SimpleXMLElement well.

Usage example is available in readme.

]]>
0
[news] Auth 1.0.0 released Tue, 25 Aug 2020 20:14:16 +0000 https://www.yiiframework.com/news/298/auth-1-0-0-released https://www.yiiframework.com/news/298/auth-1-0-0-released samdark samdark

First version of yiisoft/auth standalone package was released.

The package provides various authentication methods, a set of abstractions to implement in your application, and a PSR-15 middleware to authenticate an identity. Out of the box it provides HTTP basic authentication, HTTP bearer authentication, custom HTTP header authentication, query parameter authentication, and composite authentication that combines multiple methods. It is possible to add your own authentication methods.

More description and usage examples are available in readme.

The code follows our new standards and has 100% test coverage and high mutation score.

]]>
0
[news] Access 1.0.0 released Tue, 25 Aug 2020 20:14:33 +0000 https://www.yiiframework.com/news/297/access-1-0-0-released https://www.yiiframework.com/news/297/access-1-0-0-released samdark samdark

First version of yiisoft/access standalone package was released.

This package provides an interface for checking checking if certain user has certain permission. Optional parameters could be passed for fine-grained access checks.

Usage example is available in readme.

]]>
0
[extension] execut/yii2-books Tue, 29 Sep 2020 23:23:44 +0000 https://www.yiiframework.com/extension/execut/yii2-books https://www.yiiframework.com/extension/execut/yii2-books eXeCUT eXeCUT

yii2-books

  1. Installation
  2. Usage

Module for managing books and their authors. It is an example to demonstrate the integration of the following tools to simplify development on the Yii2 framework:

The native version of this CRUD in pure Yii2 for comparison can be viewed here execut/yii2-books-native

For license information check the LICENSE-file.

English documentation is at docs/guide/README.md.

Русская документация здесь docs/guide-ru/README.md.

Latest Stable Version Total Downloads Build Status

Installation

The preferred way to install this extension is through composer.

Install

Either run

$ php composer.phar require execut/yii2-books "dev-master"

or add

"execut/yii2-books": "dev-master"

to the `require` section of your composer.json file.

Apply module migrations: `shell script ./yii migrate/up --interactive=0 -> ...migration was applied. -> -> Migrated up successfully. `

Usage

Open books example url in your browser /books/books/index.

Books menuBooks CRUD listBooks CRUD form

Authors example here /books/authors/index.

Authors CRUD listAuthors CRUD form

For more details please refer to the documentation docs/guide/README.md.

Для более подробной информации обращайтесь к документации docs/guide-ru/README.md.

]]>
0
[extension] execut/yii2-crud Tue, 29 Sep 2020 23:23:44 +0000 https://www.yiiframework.com/extension/execut/yii2-crud https://www.yiiframework.com/extension/execut/yii2-crud eXeCUT eXeCUT

Yii2 CRUD

  1. Installation
  2. Usage

It's package for simple creating CRUD from configuring navigation to required controller actions in 3 steps without copy-paste or CRUD generators.

For license information check the LICENSE-file.

English documentation is at docs/guide/README.md.

Русская документация здесь docs/guide-ru/README.md.

Latest Stable Version Total Downloads Build Status

Installation

The preferred way to install this extension is through composer.

Install

Either run

$ php composer.phar require execut/yii2-crud "dev-master"

or add

"execut/yii2-crud": "dev-master"

to the `require` section of your composer.json file.

Add bootstrap to your application config: `php return [

'bootstrap' => [
    'yii2-crud' => [
        'class' => \execut\crud\Bootstrap::class,
    ]
]

]; `

Usage

Let's say you need to make a CRUD for the model execut\books\models\Book To do this, just add the following lines to controller: `php namespace execut\books\controllers;

use execut\books\models\Book; use execut\crud\params\Crud; use yii\web\Controller; class BooksController extends Controller {

public function actions()
{
    $crud = new Crud([
        'modelClass' => Book::class,
        'modelName' => Book::MODEL_NAME,
    ]);
    return $crud->actions();
}

} ` As a result, a full-fledged CRUD will appear for this model: Books CRUD listBooks CRUD form

For more details please refer to the documentation docs/guide/README.md.

Для более подробной информации обращайтесь к документации docs/guide-ru/README.md.

]]>
0
[news] Yii 2.0.37 Fri, 07 Aug 2020 21:11:39 +0000 https://www.yiiframework.com/news/296/yii-2-0-37 https://www.yiiframework.com/news/296/yii-2-0-37 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.37. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Version 2.0.37 fixes 9 bugs including previous release regressions. Also this release make PhpStorm IDE code-completion better and enhances the way exceptions are printed out in console application when in debug mode.

A complete list of changes can be found in the CHANGELOG.

Thanks to all Yii community members who contribute to the framework, translators who keep documentation translations up to date and community members who answer questions at forums.

There are many active Yii communities so if you need help or want to share your experience, feel free to join them.

]]>
0
[extension] kovenant/yii2-seo-components Thu, 06 Aug 2020 11:32:26 +0000 https://www.yiiframework.com/extension/kovenant/yii2-seo-components https://www.yiiframework.com/extension/kovenant/yii2-seo-components kovenant kovenant

yii2-seo-components

  1. Installation
  2. SeoModelBehavior Usage
  3. MetaTagsWidget.php Usage

Latest Version Software License Build Status Scrutinizer Code Quality codecov

Installation

The preferred way to install this extension is via composer.

Either run

php composer.phar require --prefer-dist kovenant/yii2-seo-components "*"

or add this code line to the require section of your composer.json file:

"kovenant/yii2-seo-components": "*"

SeoModelBehavior Usage

Add SeoModelBehavior to your model

{id}, {category_id} and {alias} will be replaced by the value of the attributes of the current model, that have such names

{category.alias} will be replaced by the value of the relation’s attribute

    /**
     * @return array
     */
    public function behaviors()
    {
        return [
            [
                'class' => \kovenant\seo\SeoModelBehavior::class,
                'route' => ['catalog/item', 'categoryId' => '{category_id}', 'categoryAlias' => '{category.alias}', 'id' => '{id}', 'alias' => '{alias}']
            ],
        ];
    }

Behavior provides three methods:

  • $model->getRouteUrl() will return route as array. Now you can use it for yii\widgets\Menu items 'url'
Array
(
    [0] => catalog/item
    [categoryId] => 1
    [categoryAlias] => category-alias
    [id] => 5
    [alias] => item-alias
)
  • $model->getUrl() will return for example /catalog/1-category-alias/5-item-alias.html

  • $model->getAbsoluteUrl() returns https://example.com/catalog/1-category-alias/5-item-alias.html

Don't forget to check your config file for pretty url settings

E.g.

    'components' => [
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'suffix' => '.html',
            'rules' => [
                'catalog/<categoryId:\d+>-<categoryAlias>/<id:\d+>-<alias>' => 'catalog/item',
            ],
        ],
    ],

MetaTagsWidget.php Usage

Add MetaTagsWidget to your view

/** yii\db\ActiveRecord $model */
\kovenant\seo\MetaTagsWidget::widget(['component' => $model]);

In addition to the widget settings you can configure common widget options via container definitions in your config file

Example of full options:

    'container' => [
        'definitions' => [
            'kovenant\seo\MetaTagsWidget' => [
                // set view attributes
                'viewH1Attribute' => 'h1', /* use <h1><?= $this->h1 ?></h1> in your view/layout */
                'viewTitleAttribute' => 'title', /* will produce <title> */

                // Set the model attributes
                'componentNameAttribute' => 'name', // default name attribute (e.g. for link name)
                'componentH1Attribute' => 'h1', // h1 for page
                'componentTitleAttribute' => 'title', // meta title
                'componentKeywordsAttribute' => 'keywords', // meta keywords
                'componentDescriptionAttribute' => 'description', // meta description

                // set pager params
                'pageText' => 'Страница', // page text [Page for default]
                'pageParam' => 'page', // get param for current page

                // In all templates placeholder {text} is an original value
                'templateH1' => '{text}',
                'templateTitle' => '{text}{pager} | {appName}', // {appName} will add name of application
                'templateKeywords' => '{text}',
                'templateDescription' => '{text}{pager}', // {pager} will be replaced with text about current page
                'templatePager' => ' - {pageText} {pageValue}', // template for such replacement

                // method from \kovenant\seo\SeoModelBehavior that will return absolute url for the page of this record
                'absoluteUrlMethod' => 'getAbsoluteUrl'
            ]
        ],
    ],
    'view' => [
        //you can use custom view for h1 support
        'class' => 'kovenant\seo\SeoView',
    ],

componentNameAttribute is required. Other component attributes are optional.

The text for default value of title and description is componentNameAttribute.

If viewH1Attribute and componentH1Attribute are set they will be used as a default value.

componentTitleAttribute, componentKeywordsAttribute and componentDescriptionAttribute will set corresponding meta tags.

i.e. default value = componentNameAttribute << componentH1Attribute << componentTitleAttribute

If you set absoluteUrlMethod from SeoModelBehavior and current page of widget != absolute url from model, canonical link tag will be added.

You can inherit from MetaTagsWidget and add your own getters to use in templates, like {appName} and {pager}

See more examples of usage in tests.

]]>
0
[extension] letsjump/yii2-easy-ajax Sun, 09 Aug 2020 15:58:01 +0000 https://www.yiiframework.com/extension/letsjump/yii2-easy-ajax https://www.yiiframework.com/extension/letsjump/yii2-easy-ajax letsjump letsjump

yii2-easy-ajax

Relax your keyboard with Yii2 EasyAjax

EasyAjax are a bunch of Yii methods that minimize the amount of code you need to write to interact with Bootstap UI and with Javascript in general.

Notifies, modals, tabs, pjax-reloads, form validations among others can now be set up and launched with only a line of code into the controller's action response.

For example, EasyAjax::modal('My modal content') opens a modal with "My modal content" as content, while EasyAjax::reloadPjax(['#p0']) reloads the pjax-container identified by id="p0"

EasyAjax further provides:

  • Complete and ready-to-use ajax CRUD, thanks to the integrated Gii generator
  • Fully configurable options both globally and action-specific
  • Customizable HTML templates
  • Extensible with your own methods

Installation

The preferred way to install this extension is through composer.

Either run



or add


```"letsjump/yii2-easy-ajax": "~1.0.0"```

to the `require` section of your composer.json.

## Configuration

To use this extension, add the following code to your web application configuration (config/web.php):

```php
'components' => [
    'easyAjax' => [
        'class' => 'letsjump\easyAjax\EasyAjaxBase',
        'customOptions' => [
            /* refer to plugin documentation */
        ]
    ],
]
```

To use the integrated [Gii generator](#Ajax-CRUD-with-Gii), add the following code to your application configuration:

```php
if (YII_ENV_DEV) {
    $config['bootstrap'][] = 'gii';
    $config['modules']['gii'] = [
        'class' => \yii\gii\Module::class,
        // uncomment the following to add your IP if you are not connecting from localhost.
        'allowedIPs' => ['127.0.0.1', '::1', /** your IPs */],
        'generators' => [
            'crud' => [ 
                'class'     => 'letsjump\easyAjax\gii\crud\Generator',
                'templates' => [ //setting for out templates
                    'yii2-easy-ajax' => '@vendor/letsjump/yii2-easy-ajax/gii/crud/default',
                ]
            ],
        ],
    ];
}
```

Please note: The integrated [Gii generator](#Ajax-CRUD-with-Gii) allows you to generate the code for AJAX as well as the standard CRUD.

## Usage

- [1. Requests](#1.-Requests)
- [2. Responses](2.-Responses)

### 1. Requests

EasyAjax requests are simple jQuery AJAX requests that need to refer to a controller action appropriately configured:

The easiest way to perform an EasyAjax request to a controller action is adding the `data-yea=1` attribute to the Html tag in charge of the _onclick javascript event_:

```html
<a data-yea="1" class="btn btn-lg btn-success" href="<?= \yii\helpers\Url::to(['controller/action-notify']) ?>">notify something</a>
```

or

```html
<button data-yea="1" data-form-id="friend-form" type="submit" class="btn btn-primary pull-right">Save</button>
```

> NOTE:
> 
> To explore any other Html attributes available and to know all the details, please refer to the [guide](docs/guide/requests.md)

### 2. Responses

The controller actions interacting with EasyAjax should return a _Json array_, and they only need to contain one or more EasyAjax methods in their response array. 

In the following example...

```php
public function actionSaveMyModel()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;

    // your model validation and save logic...

    return [
        \letsjump\easyAjax\EasyAjax::modalClose(),
        \letsjump\easyAjax\EasyAjax::reloadPjax(['#p0']),
        \letsjump\easyAjax\EasyAjax::notifySuccess('Your model has been saved')
    ];
}
```
... EasyAjax will:
- Closes the [bootstrap modal](https://getbootstrap.com/docs/3.4/javascript/#modals) opened in the UI
- [Reload](https://github.com/yiisoft/jquery-pjax#fnpjax) the pjax-container `#p0`
- Shows a [Bootstrap Notify](http://bootstrap-notify.remabledesigns.com/) informing the user of the successful operation

Here are the available methods in detail:

#### Modals

With EasyAjax, you can fire and configure a [Bootstrap Modal](https://getbootstrap.com/docs/3.4/javascript/#modals) using the following: <code>EasyAjax::modal(content, title, _form_id, size, footer, options_)</code>

```php
public function actionModal()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    
    return [
        \letsjump\easyAjax\EasyAjax::modal('This is the modal content', 'Modal title'),
    ];
}
```

It is also available a method to remotely close a Modal which is actually open in the UI:

```php
public function actionCloseModal()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    
    return [
        \letsjump\easyAjax\EasyAjax::modalClose(),
    ];
}

```

 > You can edit the modal layout and many other settings. Please refer to the [guide](docs/guide/modals.md) for all the available options.

---

#### Notify

**EasyAjax Notify** allows to control the [Bootstrap Notify plugin](http://bootstrap-notify.remabledesigns.com/). The plugin assets (`bootstrap-notify.js` and `animate.js`) are bundled within EasyAjax. 

> Tip: You can disable the asset inclusion if it is already bundled in your application

To fire a Notify, you can use <code>\letsjump\easyAjax\EasyAjax::notify(message, *title*, *settings*)</code>.
In the `settings` parameter, you should specify the type of notification displayed (`Notify::TYPE_INFO` (blue), `Notify::TYPE_SUCCESS` (green), `Notify::TYPE_WARNING` (yellow) or `Notify::TYPE_DANGER` (red)).

To speed up your coding, I have also added some shortcut methods for the most common notification types: `EasyAjax::notifyInfo(message)`, `EasyAjax::notifySuccess(message)`, `EasyAjax::notifyWarning(message)`, `EasyAjax::notifyDanger(message)`.

Example:

```php
public function actionNotify()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    return [
        \letsjump\easyAjax\EasyAjax::notifyInfo('This is an Info Notify!')
    ];
}
```

> Note: Actually notify doesn't refer to the [Yii2 flash message session array](https://www.yiiframework.com/doc/api/2.0/yii-web-session#$flash-detail). Please open an issue if you think it is needed.
>
> Refer to the [guide](docs/guide/notify.md) for all the available options and template manipulation.

---
#### Pjax Reload

The EasyAjax PjaxReload (`EasyAjax::pjaxReload(['#myPjaxID0', '#myPjaxID1', ...])`) method allows to reload one or more Pjax containers.

```php
public function actionPjaxReload()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    
    return [
        \letsjump\easyAjax\EasyAjax::reloadPjax(['#p0'])
    ];
}
```

> Refer to the [guide](docs/guide/pjax-reload.md) for detailed informations.

---
#### Form Validation

The form validation (`EasyAjax::formValidation(['#my-form-id'=>ActiveForm::validate(MyModelClass)])`) method allows to display the validation results for the specified form. It will send the validation results for each form field specified by the `#form-id` attribute to [display its error messages](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md).

```php
public function actionValidateForm()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    $contactForm                = new ContactForm();
    
    return [
        \letsjump\easyAjax\EasyAjax::formValidation(['#contact-form' => \yii\widgets\ActiveForm::validate($contactForm)])
    ];
}
``` 
> Refer to the [guide](docs/guide/form-validation.md) for detailed informations.

---
#### Ajax CRUD with Gii

The bundled Gii generator within EasyAjax allows you to instantly create the Ajax CRUD controller and views. 

![Gii module](docs/images/gii.jpg)

This will generate a CRUD schema which is identical to the Yii2 original one, with an added `actionModal($id=null)` into the controller code, plus its relative <code>_myViewFolder_/_modal.php</code> view in charge of creating or updating the data in an Ajax modal.

You can switch the CRUD behavior to Ajax modal by simply setting the 'modal' parameter of the integrated ActionColumn method : 

```php
// myViewFolder/index.php 

<?= \yii\grid\GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'],
        'id',
        // ... your gridview fields
        [
            'class' => 'letsjump\easyAjax\helpers\ActionColumn',
            'modal' => true
        ],
    ],
]); ?>
```

> BONUS: Deleting a record doesn't imply a complete page refresh, therefore the GridView pagination will not be affected.
> 
> Refer to the [guide](docs/guide/gii.md) for all the available options.

---

#### Ajax Tabs

EasyAjax Tab (`EasyAjax::tab('tab-id', 'content to inject')`) is a simple and _very basic_ way to update the content of a [Bootstrap Tab](https://getbootstrap.com/docs/3.4/javascript/#tabs) with an ajax response from a controller.

**View**

In the \yii\bootstrap\Tabs widget for each _tab item_, you must:
- Include a `linkOptions` parameter with a reference to the controller action Url and a `data-yea=1` parameter which will fire the request. 
- Set the tab container ID (`'options'     => ['id' => 'yourTabID']`) for a stable reference to its content.

```php
<?= \yii\bootstrap\Tabs::widget([
    'items' => [
        [
            'label'       => 'Rome',
            'linkOptions' => [
                'data-href' => \yii\helpers\Url::to(['site/tab', 'id' => 'rome',]),
                'data-yea'  => 1
            ],
            'options'     => ['id' => 'rome'],
        ],
        // ... other tabs
    ]
]) ?>
```

**Controller**

Just include the EasyAjax method `EasyAjax::tab(tab-id, content)` into the controller action response array:

```php
public function actionTab($id)
{
    $date = new \DateTimeImmutable('now');
    
    $content = [
        'rome' => $date->setTimezone(new \DateTimeZone('Europe/Rome'))->format('l jS \of F h:i:s A'),
        'london' => $date->setTimezone(new \DateTimeZone('Europe/London'))->format('l jS \of F h:i:s A'),
        'new-york' => $date->setTimezone(new \DateTimeZone('America/New_York'))->format('l jS \of F h:i:s A'),
        'calcutta' => $date->setTimezone(new \DateTimeZone('Asia/Calcutta'))->format('l jS \of F h:i:s A'),
    ];
    
    if ( ! array_key_exists($id, $content)) {
        throw new \yii\web\BadRequestHttpException('Your request is invalid');
    }
    
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    
    return [
        \letsjump\easyAjax\EasyAjax::tab($id, '<p>' . $content[$id] . '</p>')
    ];
}
```

> Warning: tab-id must be specified without the hashtag (#).
>
> Refer to the [guide](docs/guide/tabs.md) for all the available options.

---
#### Content replace
 
EasyAjax `ContentReplace(['#container1-id'=>'New content', '#container2-id'=>'New content', ...])` replaces the content of a specific Html tag with the code sent by the EasyAjax response. It uses the [`jQuery.html()` standard method](https://api.jquery.com/html/). 
 
```php
public function actionContentReplace()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    
    return [
        \letsjump\easyAjax\EasyAjax::contentReplace(['#time-placeholder' => date('d/m/Y H:i:s')])
    ];
}
```

> Refer to the [guide](docs/guide/content-replace.md) for detailed informations.

---
#### Confirms

You can use the EasyAjax `confirm('message', 'url')` method to fire a [Javascript Window confirm()](https://www.w3schools.com/jsref/met_win_confirm.asp) from a controller action. By clicking on the "OK" button it will fire an Ajax request to the action specified in the `url` parameter.
```php
public function actionConfirm()
{
    Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    
    return [
        \letsjump\easyAjax\EasyAjax::confirm('This will fire a growl. Ok?', \yii\helpers\Url::to(['site/notify']))
    ];
}
```

> Refer to the [guide](docs/guide/confirm.md) for detailed informations.

## Security
See [SECURITY.md](SECURITY.md)

## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)

## Credits

[Yiisoft](https://www.yiiframework.com/) for the best PHP framework for large-scale application (my own opinion), [Twitter Bootstrap](https://getbootstrap.com/), and the [Bootstrap Notify plugin](http://bootstrap-notify.remabledesigns.com/). The Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org)

## License

See [LICENSE.md](LICENSE.md)

]]>
0
[extension] execut/yii2-crud-fields Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/extension/execut/yii2-crud-fields https://www.yiiframework.com/extension/execut/yii2-crud-fields eXeCUT eXeCUT

eXeCUT Yii2 CRUD fields

  1. Installation
  2. Usage

This component allows you to automate many processes that occur in working with models, thereby reducing code duplication, and hence the total time spent:

  • Writing validation rules for fields of the same type
  • Writing Getters to Declare Various Relationships with Other Models
  • Validating and Editing Linked Records
  • Setting up the edit form for the model and its associated records
  • Customizing the Model Record List
  • Translating field names
  • Adds the ability to extend models by a third-party module without adding new dependencies
  • Simplifies the process of unit testing models

For license information check the LICENSE-file.

English documentation is at docs/guide/README.md.

Русская документация здесь docs/guide-ru/README.md.

Latest Stable Version Total Downloads Build Status

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require execut/yii2-crud-fields

or add

"execut/yii2-crud-fields": "dev-master"

to the require section of your composer.json file.

Usage

For example, the following few lines of code in a model:

namespace execut\books\models;
class Book extends \yii\db\ActiveRecord {
    use \execut\crudFields\BehaviorStub;
    public function behaviors() {
        return [
            \execut\crudFields\Behavior::KEY => [
                'class' => \execut\crudFields\Behavior::class,
                'fields' => [
                    'id' => [
                        'class' => \execut\crudFields\fields\Id::class,
                    ],
                    'name' => [
                        'class' => \execut\crudFields\fields\StringField::class,
                        'attribute' => 'name',
                        'required' => true,
                    ]
                ],
            ],
        ];
    }
}

will make all required for CRUD:

`php $model = new Book(); echo 'Validation rules for the search and edit scenario'; var_dump($model->rules()); echo 'Forming ActiveQuery based on filtering parameters and configuring ActiveDataProvider'; var_dump($model->search()); echo 'Formation of list columns settings'; var_dump($model->getGridColumns()); echo 'Formation of settings for the creation/editing form'; var_dump($model->getFormFields()); `

Books CRUD listBooks CRUD form

If we compare the implementation of such a model with a model without extension, we can see that the amount of code has changed in a positive direction:

Model on native Yii2 (85 lines) vs Model on CRUD fields (36 lines)

Or more strong example with books authors:

Model on native Yii2 (370 lines) vs Model on CRUD fields (116 lines)

Authors CRUD listAuthors CRUD form

For more details please refer to the documentation docs/guide/README.md.

Для более подробной информации обращайтесь к документации docs/guide-ru/README.md.

]]>
0
[wiki] Start using Yii2 in Raspberry Pi3 (RPI3) via Pantahub Tue, 04 Aug 2020 12:18:44 +0000 https://www.yiiframework.com/wiki/2557/start-using-yii2-in-raspberry-pi3-rpi3-via-pantahub https://www.yiiframework.com/wiki/2557/start-using-yii2-in-raspberry-pi3-rpi3-via-pantahub sirin_ibin sirin_ibin
  1. Make your RPI3 device ready to deploy Yii2 by following 6 Steps
  2. Deploy your Yii2 app to the device by following 5 Steps

Note:Pantahub is the only place where Linux firmware can be shared and deployed for any device, You can signup @pantahub here:http://www.pantahub.com

Make your RPI3 device ready to deploy Yii2 by following 6 Steps

Step 1: Burn the RPI3 initial stable image into your sd card.
a) Download RPI3 image

Click to download: https://pantavisor-ci.s3.amazonaws.com/pv-initial-devices/tags/012-rc2/162943661/rpi3_initial_stable.img.xz

b) unxz the device image

Run $ unxz rpi3_initial_stable.img.xz

c) Burn image into sd card using Raspberry Pi Imager 1.2

Step 2: Boot your RPI3
a) Insert your sd card and supply the power

Step 3: Singup @pantahub here http://www.pantahub.com
Step 4: Download & Install a CLI tool "pvr"

Note: pvr is a CLI tool which can be used to interact with your device through pantahub platform.

Note: Using pvr you can share your firmware and projects as simple as with a git tree.

Note: Move the pvr binary to your bin folder after download.

Linux(AMD64): Download

Linux(ARM32v6): Download

Darwin(AMD64): Download

pvr clone; pvr commit; pvr post

To install from github source code: $ go get gitlab.com/pantacor/pvr $ go build -o ~/bin/pvr gitlab.com/pantacor/pvr

Note: You need "GOLANG" to be installed in your system for building pvr from github source code.

Step 5: Detect & Claim your device
a) Connect a LAN cable between your RPI3 & computer/Router.

b) Open your terminal & run $ pvr scan

c) Claim your device

$ pvr claim -c merely-regular-gorilla https://api.pantahub.com:443/devices/5f1b9c44e193a Watch now on Amazon Prime Video 5000afa9901

d) Log into Panthub.com and check whether the newly claimed device appeared in the dashboard or not.

Step 6: Clone the device to your computer using the Clone URL of your device

$ pvr clone https://pvr.pantahub.com/sirinibin/presently_learning_pelican/0 presently_learning_pelican

Now your device is ready to deploy your Yii2 app

Deploy your Yii2 app to the device by following 5 Steps

Step 1: Move to device root dir
 `$ cd presently_learning_pelican`
Step 2: Add a new app "yii2" into the device

>sirinibin/yii2-basic-arm32v7:latest is a Docker Watch now on Amazon Prime Video image made for the devices with ARM32 architecture >> You can customise the docker image for your custom Yii2 app.

$ pvr app add yii2 --from=sirinibin/yii2-basic-arm32v7:latest

Step 3: Deploy the changes to the device

$ pvr add . $ pvr commit $ pvr post

Step 4: Check the device status changes in Pantahub.com dashboard & wait for the status to become "DONE"

Status 1:

Status 2:

Status 3:

Status 4:

Step 5: Verify the "yii2" app deployment

Access the device IP: http://10.42.0.231/myapp1/web/ in your web browser.

You are done!

]]>
0
[news] ElasticSearch extension 2.1.0 released Wed, 16 Sep 2020 09:41:31 +0000 https://www.yiiframework.com/news/295/elasticsearch-extension-2-1-0-released https://www.yiiframework.com/news/295/elasticsearch-extension-2-1-0-released samdark samdark

We are very pleased to announce the release of ElasticSearch extension version 2.1.0.

This is the first stable release with support for ElasticSearch from version 5 to current version 7.

We have switched to GitHub actions for testing it. Extension is tested on PHP 5.6 - PHP 7.4 and ElasticSearch 5.6.16, 6.8.9, and 7.7.0.

The extension guide was updated and completed.

See the CHANGELOG for details.

Special thanks to Konstantin Sirotkin, @beowulfenator who managed to complete this release doing major part of the work.

]]>
0
[extension] execut/yii2-migration Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/extension/execut/yii2-migration https://www.yiiframework.com/extension/execut/yii2-migration eXeCUT eXeCUT

yii2-migration

  1. Installation
  2. Usage
  3. Supported databases

This is a typical migration for yii2: `php

public function safeUp()
{
    $this->createTable('characteristics_units', [
        'id' => $this->primaryKey(),
        'name' => $this->string()->notNull(),
        'short_name' => $this->string()->notNull(),
        'created' => $this->dateTime()->notNull()->defaultExpression('now()'),
        'updated' => $this->dateTime(),
    ]);

    $this->createTable('characteristics', [
        'id' => $this->primaryKey(),
        'characteristics_unit_id' => $this->integer()->notNull(),
        'name' => $this->string()->notNull(),
        'created' => $this->dateTime()->notNull()->defaultExpression('now()'),
        'updated' => $this->dateTime(),
    ]);

    $this->addForeignKey('characteristics_unit_id_characteristics_fk', 'characteristics', 'characteristics_unit_id', 'characteristics_units', 'id');
}

public function safeDown()
{
    $this->dropTable('characteristics');
    $this->dropTable('characteristics_units');
}
Why write more? If you use execut yii2-migration helper, you can write it faster and more compact:
```php
    public function initInverter(\execut\yii\migration\Inverter $i)
    {
        $i->table('characteristics')->create(array_merge($this->defaultColumns(), [
            'name' => $this->string()->notNull(),
            'short_name' => $this->string()->notNull(),
        ]));

        $i->table('characteristics_units')->create(array_merge($this->defaultColumns(), [
            'name' => $this->string()->notNull(),
        ]))->addForeignColumn('characteristics');
    }

Installation

The preferred way to install this extension is through composer.

Install

Either run

$ php composer.phar require execut/yii2-migration "dev-master"

or add

"execut/yii2-migration": "dev-master"

to the `require` section of your composer.json file.

Usage

To use yii2 migration, simply expand the migration class from the execut\yii\migration\Migration class and override the abstract method: `php

 public function initInverter(\execut\yii\migration\Inverter $i)
 {
 }
$i has all the methods that normal migration has, but allows you to write actions up and down at a time.

To permanently do not rewrite the migration, you can define a new template for the yii migrate\create command.
```php
    'controllerMap' => [
        'migrate' => [
            'templateFile' => '@vendor/execut/yii2-migration/views/template.php',
        ],

Supported databases

Currently only supported PostgreSQL and MySQL. Also you can use BDR plugin for PostgreSQL.

]]>
0
[news] ApiDoc extension version 2.1.5 released Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/news/294/apidoc-extension-version-2-1-5-released https://www.yiiframework.com/news/294/apidoc-extension-version-2-1-5-released samdark samdark

We are very pleased to announce the release of the ApiDoc extension version 2.1.5.

This release fixes some issues using extension with PHP 7.

See the CHANGELOG for a full list of changes.

]]>
0
[extension] execut/yii2-navigation Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/extension/execut/yii2-navigation https://www.yiiframework.com/extension/execut/yii2-navigation eXeCUT eXeCUT

Yii2 navigation extension

  1. Installation
  2. Usage

Extension for managing everything related to navigation. Has widgets for display breadcrumbs with support microdata scheme, SEO tags, menus, title and text of the active page according to the given hierarchy. What unties all modules from each other necessary due to the possibility of dynamic customization.

For license information check the LICENSE-file.

English documentation is at docs/guide/README.md.

Русская документация здесь docs/guide-ru/README.md.

Latest Stable Version Total Downloads Build Status

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require execut/yii2-navigation

or add

"execut/yii2-navigation": "dev-master"

to the require section of your composer.json file.

Usage

For example, the following few lines of code in a view file would render a common attributes of active page:

// head
echo \execut\navigation\widgets\Title::widget();
// body
echo \execut\navigation\widgets\Breadcrumbs::widget();
echo \execut\navigation\widgets\Header::widget();
echo \execut\navigation\widgets\Text::widget();
echo \execut\navigation\widgets\Time::widget();
]]>
0
[extension] vjik/yii2-rules-validator Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/extension/vjik/yii2-rules-validator https://www.yiiframework.com/extension/vjik/yii2-rules-validator vjik vjik

Yii2 validator with nested rules

  1. Installation
  2. Examples

Installation

The preferred way to install this extension is through composer:

composer require vjik/yii2-rules-validator

Examples

Use in model
class MyModel extends Model
{

    public $country;

    public function rules()
    {
        return [
            [
                'country',
                RulesValidator::class,
                'rules' => [
                    ['trim'],
                    ['string', 'max' => 191],
                    ['validateCountry'],
                ],
            ],
        ];
    }

    public function validateCountry($attribute, $params, $validator)
    {
        if (!in_array($this->$attribute, ['Russia', 'USA'])) {
            $this->addError($attribute, 'The country must be either "Russia" or "USA".');
        }
    }
}
Rule Inheritance

Rule class:

class MyRulesValidator extends RulesValidator
{

    protected function rules(): array
    {
        return [
            ['trim'],
            ['string', 'max' => 191],
            ['validateCountry'],
        ];
    }

    public function validateCountry($model, $attribute, $params, $validator)
    {
        if (!in_array($model->$attribute, ['Russia', 'USA'])) {
            $model->addError($attribute, 'The country must be either "Russia" or "USA".');
        }
    }
}

Model:

class MyModel extends Model
{

    public $country;

    public function rules()
    {
        return [
            ['country', MyRulesValidator::class],
        ];
    }
}
]]>
0
[news] Yii 2.0.36 Tue, 07 Jul 2020 22:08:55 +0000 https://www.yiiframework.com/news/293/yii-2-0-36 https://www.yiiframework.com/news/293/yii-2-0-36 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.36. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Version 2.0.36 is an interesting release.

  • A big part of it is MSSQL-related fixes made by @DarkDef. Additionally to these, he added MSSQL tests to GitHub actions pipeline so what's covered by tests won't break anymore.
  • Regressions in ArrayHelper::getValue() and EachValidator from last release were fixed.
  • Mutex::isAcquired() was added so you can check if mutex is acquired without acquiring it.
  • jQuery 3.5.0 compatibility was declared so this version can now be used.
  • Hindi translation for messages was added.

Last but not least, there are two additions to dependency injection container that are bringing Yii 2 container closer in capabilities to what we have in Yii 3.

First, action injection was implemented by @SamMousa, @ErickSkrauch, and @samdark. In both web controller actions and console controller actions, the following now is possible:

namespace app\controllers;

use yii\web\Controller;
use app\components\BookingInterface;

class HotelController extends Controller
{    
    public function actionBook($id, BookingInterface $bookingService)
    {
        $result = $bookingService->book($id);
        // ...    
    }
}

Another enhancement brought by @hiqsol is that references are now resolved in arrays:

return [
    ContentTypeMiddleware::class => [
        '__construct()' => [
            Instance::of(StreamFactory::class),
            [
                'json' => Intance::of(JsonFormatter::class),
                'yaml' => Intance::of(YamlFormatter::class),
            ],
        ],
    ],
];

There are more fixes included. A complete list of changes can be found in the CHANGELOG.

Thanks to all Yii community members who contribute to the framework, translators who keep documentation translations up to date and community members who answer questions at forums.

There are many active Yii communities so if you need help or want to share your experience, feel free to join them.

]]>
0
[news] Yii 2 Composer Installer 2.0.10 Wed, 24 Jun 2020 00:07:35 +0000 https://www.yiiframework.com/news/292/yii-2-composer-installer-2-0-10 https://www.yiiframework.com/news/292/yii-2-composer-installer-2-0-10 samdark samdark

Yii 2 Composer plugin that handles many extension-related things behind the scenes, was updated to support parallel unzipping introduced in upcoming Composer 2.

Update should not require any specific actions to be performed except issuing composer update command.

]]>
0
[news] Aliases 1.0.0 released Mon, 08 Jun 2020 15:29:17 +0000 https://www.yiiframework.com/news/291/aliases-1-0-0-released https://www.yiiframework.com/news/291/aliases-1-0-0-released samdark samdark

First version of yiisoft/aliases standalone package was released.

The package aim is to store path aliases, i.e. short name representing a long path (a file path, a URL, etc.). Path alias value may have another value as its part. For example, @vendor may store path to vendor directory while @bin may store @vendor/bin.

More description and usage examples are available in readme.

The code has 100% test coverage and 100% mutation score i.e. it is very stable.

]]>
0
[news] ElasticSearch extension 2.0.7 released Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/news/290/elasticsearch-extension-2-0-7-released https://www.yiiframework.com/news/290/elasticsearch-extension-2-0-7-released samdark samdark

We are very pleased to announce the release of ElastiSearch extension version 2.0.7. This release fixes a couple of bugs and adds PHP forward compatibility to the legacy 2.0.x branch that is compatible with ElasticSeach from 1.6.0 to 1.7.6.

See the CHANGELOG for details.

]]>
0
[news] ElasticSearch extension 2.0.6 released Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/news/289/elasticsearch-extension-2-0-6-released https://www.yiiframework.com/news/289/elasticsearch-extension-2-0-6-released samdark samdark

We are very pleased to announce the release of ElastiSearch extension version 2.0.6. This release fixes a couple of bugs and adds forward compatibility to the legacy 2.0.x branch that is compatible with ElasticSeach from 1.6.0 to 1.7.6.

See the CHANGELOG for details.

]]>
0
[news] Redis extension 2.0.13 released Sat, 02 May 2020 12:05:57 +0000 https://www.yiiframework.com/news/287/redis-extension-2-0-13-released https://www.yiiframework.com/news/287/redis-extension-2-0-13-released samdark samdark

We are very pleased to announce the release of Redis extension version 2.0.13.

This version adds Redis 5.0 stream commands.

See the CHANGELOG for details.

]]>
0
[news] Gii extension 2.2.1 released Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/news/286/gii-extension-2-2-1-released https://www.yiiframework.com/news/286/gii-extension-2-2-1-released samdark samdark

We are very pleased to announce the release of Gii extension version 2.2.1.

This release allows the usage of anonymous generators.

See the CHANGELOG for details.

]]>
0
[news] ApiDoc extension version 2.1.4 released Tue, 29 Sep 2020 23:23:49 +0000 https://www.yiiframework.com/news/285/apidoc-extension-version-2-1-4-released https://www.yiiframework.com/news/285/apidoc-extension-version-2-1-4-released samdark samdark

We are very pleased to announce the release of the ApiDoc extension version 2.1.4.

This release add support for @property and @method annotations.

See the CHANGELOG for a full list of changes.

]]>
0
[news] Shell extension 2.0.4 released Sat, 02 May 2020 11:46:01 +0000 https://www.yiiframework.com/news/284/shell-extension-2-0-4-released https://www.yiiframework.com/news/284/shell-extension-2-0-4-released samdark samdark

We are very pleased to announce the release of Shell extension version 2.0.4. This version allows installing psy/psysh ~0.10.3.

]]>
0
[news] Yii 2.0.35 Mon, 04 May 2020 11:05:11 +0000 https://www.yiiframework.com/news/283/yii-2-0-35 https://www.yiiframework.com/news/283/yii-2-0-35 samdark samdark

We are very pleased to announce the release of Yii Framework version 2.0.35. Please refer to the instructions at https://www.yiiframework.com/download/ to install or upgrade to this version.

Version 2.0.35 fixes 13 issues and adds some small enhancements:

  • Query::withQuery() method that can be used for CTE.
  • yii\i18n\Formatter::$currencyDecimalSeparator to allow setting custom symbols for currency decimal in IntlNumberFormatter.
  • SameSite for cookies now works on PHP < 7.3.

Thanks to all Yii community members who contribute to the framework, translators who keep documentation translations up to date and community members who answer questions at forums.

There are many active Yii communities so if you need help or want to share your experience, feel free to join them.

A complete list of changes can be found in the CHANGELOG.

]]>
0
[wiki] Yii2 - Upgrading to Bootstrap 4 Fri, 20 Mar 2020 12:18:55 +0000 https://www.yiiframework.com/wiki/2556/yii2-upgrading-to-bootstrap-4 https://www.yiiframework.com/wiki/2556/yii2-upgrading-to-bootstrap-4 RichardPillay RichardPillay

Yii2 - Converting from Bootstrap3 to Bootstrap4

This article has been written because while conversion is a largely pain-free process, there are some minor issues. These are not difficult to solve, but it is not immediately obvious where the problem lies.

1 - Install Bootstrap4 My preference is to simply use composer. Change composer.json in the root of your project:

  • find the line that includes Bootstrap3.
  • Copy the line, and change the new line:

    • change bootstrap to bootstrap4
    • Now head over to https://github.com/yiisoft/ - the Yii2 repository on Github
    • Change the version string to that version number, and also change the ~ to ^
    • After this, you should have something like this below, maybe with higher version numbers:

      "yiisoft/yii2-bootstrap" : "~2.0.6", "yiisoft/yii2-bootstrap4" : "^2.0.8",

  • Save the file, then run 'composer update'
  • Use your IDE, text editor or whatever other means you have at your disposal to find all occurrences where bootstrap is used and change it bootstrap4. My suggestion is to search for the string yii\bootstrap\ and change it to yii\bootstrap4\. However, be careful - your IDE may require the backslash to be escaped. For example, Eclipse will find all files with the string easily, but the search string must have double-backslashes, while the replacement string must be left as single ones.
  • Now run all your tests and fix the problems that have arisen. Most of the failures will come from the fact that the Navbar no longer works, most likely. It's still there, just being rendered differently, and in my case it was invisible. This is because Bootstrap4 has changed some elements of the navbar. In my case, 14 tests failed - many of which failed due to the use of navbar content in some manner, such as looking for the Login link to infer whether the user is logged in or not.
    • You're not going to fix these issues without understanding them, so take a look at https://getbootstrap.com/docs/4.0/migration/. In particular, look at what has changed regarding the Navbars. The most meaningful is that Bootstrap4 no longer specifies a background, where Bootstrap3 did.
    • Open up frontend/viewslayouts/main.php in your editor, then look at your site in the browser. In my case no navbar, except for the Brand, and that is because I changed this from what was delivered as part of the Yii2 template, and it included a background. Since the rest of it was standard, there was nothing else - no ribbon strip, and no menu entries, although mousing into the area would show the links were there and could be clicked.
      • Find the line that starts with NavBar::begin and look at it's class options. In my case they were the original: 'class' => 'navbar-inverse navbar-fixed-top'
        • As I said before, no background is included, so add one: bg-dark - and check again. Now there's the ribbon back again, but no menu items.
        • Right-click on the ribbon and select "Inspect Element", or do whatever you have to do in your browser to inspect the page source. Looking at that starts to give you clues over what the navbar is doing. Looking at that, and referring back to the Bootstrap4 migration guide, I had the impression that neither navbar-inverse nor navbar-fixed-top were doing anything. So I removed those, and when refreshing the page, confirmed there were no changes.
        • More reading on the Bootstrap website gave me the bg-dark I mentioned earlier, and for the text, navbar-light or navbar-dark produced results.
        • now I had no menu items, but I did have a button that expanded the menu. Inspecting it's properties told me it was 'navbar-toggler', and the Bootstrap website told me it new to Bootstrap4, while it and was collapsed by default, 'navbar-expand' would expand it by default. That's cool - I reckon I'm going to add a setting for logged-in users that let them choose which they prefer. In the end, I opted for navbar-expand-md, which keeps it expanded unless the screen width is tight.

At the end of all this, I had the class line changed to something which gave me a navbar very similar to the original:

        //Bootstrap3: 'class' => 'navbar-inverse navbar-fixed-top',
        //Changed for Bootstrap4: 
        'class' => 'navbar navbar-expand-md navbar-light bg-dark',


Breadcrumbs

Note - March 2020: This entire section on Breadcrumbs may no longer be an issue. While I've left the section in as a tutorial, before making any changes read what Davide has to say in the user comments.

So, that fixed my navbar. Next, I noticed that the breadcrumbs were not quite right - the slash separating the path elements was no longer there. Preparing for a lot of debugging, I went to the Bootstrap site to look for a little inspiration. I didn't need to look any further - Bootstrap 4 requires each Breadcrumb element to have a class of "breadcrumb-item". After I spent a little time looking at vendors/yiisoft/yii2/widgets/Breadcrumbs.php to get some understanding of the issue, I discovered all that's needed is to change the itemTemplate and activeItemTemplate. Of course, since these are part of the Yii2 framework, you don't want to change that file, otherwise, it will probably get updated at some stage, and all your changes would be lost. Since both of these attributes are public, you can change them from outside the class, and the easiest place to do this is in frontend/views/main.php: `html

<div class="container">
    <?= Breadcrumbs::widget([
        'itemTemplate' => "\n\t<li class=\"breadcrumb-item\"><i>{link}</i></li>\n", // template for all links
        'activeItemTemplate' => "\t<li class=\"breadcrumb-item active\">{link}</li>\n", // template for the active link
        'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
    ]) ?>
    <?= Alert::widget() ?>
    <?= $content ?>
</div>```


Data Grid ActionColumn One of my pages was a data grid generated for me by gii. On each row it has a set of buttons that you can click to view, edit or delete the row. Under Bootstrap 4, the ActionColumn disappeared. Viewing the page source showed me it was there, but I couldn't see it or click on it. Going to the migration guide, it turns out that Bootstrap 3 includes icons, but Bootstrap 4 doesn't. I got a lot of help from a question asked in the Yii2forum.. In the end, my solution was to get a local copy of FontAwesome 5 by including the line "fortawesome/font-awesome": "^5.12.1" in the require section of composer.json, and then choosing the icons that I wanted. I spent a lot of time figuring out how to do this, but when I was done, it seemed almost anti-climactic in it's simplicity. This is what I did in my data form:

            ['class' => 'yii\grid\ActionColumn',
                'buttons' => [
                    'update' =>  function($url,$model) {
                        return Html::a('<i class="fas fa-edit"></i>', $url, [
                            'title' => Yii::t('app', 'update')
                        ]);
                    },
                    'view' =>  function($url,$model) {
                        return Html::a('<i class="fas fa-eye"></i>', $url, [
                            'title' => Yii::t('app', 'view')
                        ]);
                    },
                    'delete' => function($url,$model) {
                        return Html::a('<i class="fas fa-trash"></i>', $url, [
                            'title' => Yii::t('app', 'delete')
                        ]);
                    }
                 ]
            ],


Functional Tests

Not seeing anything more visually, at least nothing that was obvious, I now ran the suite of tests. These were all previously passing, but now several of them failed. One of them was the Contact Form, so I ran that one separately and the tests informed me they were failing because they couldn't see the error message:

1) ContactCest: Check contact submit no data
 Test  ../frontend/tests/functional/ContactCest.php:checkContactSubmitNoData
 
 Step  See "Name cannot be blank",".help-block"
 
 Fail  Element located either by name, CSS or XPath element with '.help-block' was not found.


I, on the other hand, could see the error messages on the form, so I used the browser's page source and discovered that the css class was no longer "help-block", it had changed to "invalid-feedback". Easy enough - in frontend/tests/_support/FunctionalTester.php, I changed the expected css class:

public function seeValidationError($message)
{
    $this->see($message, '.invalid-feedback');
}

Of course, this little excerpt is just an example. I found the same thing had to be done in several locations, but all were easily found and resolved.


After this, running my tests pointed me to no other problems, but I don't expect that to mean there aren't any other problems. While everything seems to be working so far, I expect there are more issues hiding in the woodwork. Somehow, those problems don't seem quite so insurmountable anymore.

]]>
0
[wiki] UUID instead of an auto-increment integer for ID with Active Record Wed, 22 Apr 2020 13:09:03 +0000 https://www.yiiframework.com/wiki/2555/uuid-instead-of-an-auto-increment-integer-for-id-with-active-record https://www.yiiframework.com/wiki/2555/uuid-instead-of-an-auto-increment-integer-for-id-with-active-record grigori grigori

I have a dream ... I am happy to join with you today in what will go down in history as the greatest demonstration of bad design of Active Record.

I have an API. It's built with a RESTful extension over Active Record, and some endpoints provide PUT methods to upload files. By a REST design we create an entity with POST /video first, and then upload a video file with PUT /video/{id}/data.

How do we get the {id}? The essential solutuion is UUID generated by a client. It allows API application to be stateless and scale it, use master-master replication for databases and feel yourself a modern guy. If you have Postgres — lucky you, feel free to use the built-in UUID data type and close this article. With MySQL the essential solution is insert into users values(unhex(replace(uuid(),'-',''))... MySQL team recommends updating our INSERT queries. With Active Record it is not really possible. For fetching UUIDs it recommends adding a virtual column — this can be used.

If you design the application from ground up, you can use defferent fields for a binary and text representation of UUID, and reference them in different parts of an application, but I am bound to the legacy code.

Adding getId()/setId() won't help - data comes from a client in JSON and fills the model object with a setAttributes() call avoiding generic magic methods.

Here's the hack:

Step 1. Add a private $idText property

use yii\db\ActiveRecord;
class Video extends ActiveRecord
{
    private $idText;

Step 2. Add two validators and a filter

//check if value is a valid UUID
['id','match', 'pattern'=>'/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i'],
// convert UUID from text value to binary and store the text value in a private variable,
// this is a workaround for lack of mapping in active record
['id','filter','skipOnError' => true, 'filter' => function($uuid) {
    $this->idText = $uuid;
    return pack("H*", str_replace('-', '', $uuid));
}],
//now let's check if ID is taken
['id','unique','filter' => function(\yii\db\Query $q) {
    $q->where(['id' => $this->getAttribute('id')]);
}],

First rule is a validator for an input. Second rule is a filter preparing UUID to be written in a binary format and keeping the text form for output. Third one is a validator running a query over the binary value generated by a filter.

Note: I wrote $this->getAttribute('id'), $this->id returns a text form.

We can write a query to validate data, not to save it.

Step 3. Add getters

public function __get($name)
{
    return ($name === 'id') ? $this->getId() : parent::__get($name);
}

/**
 * Return UUID in a textual representation
 */
public function getId(): string
{
    if ($this->idText === NULL && $this->getIsNewRecord()){
        //the filter did not convert ID to binary yet, return the data from input
        return strtoupper($this->getAttribute('id'));
    }
    //ID is converted
    return strtoupper($this->idText ?? $this->getAttribute('id_text'));
}

When we call the $model->id property we need the getId() executed. But Active Record base class overrides Yii compoent default behavior and does not call a getter method of an object if a property is a field in a table. So I override the magic getter. From the other hand, a regexp valiator I wrote calls $model->id, triggering the getter before the UUID is saved to the private property. I check if the object is newly created to serve the text value for validator.

Note the strtoupper() call: client may send UUID in both upper and low cases, but after unpacking from binary we will have a value in upper case. I received different string values before storing data to DB and after fetching it. Convert the textual UUID value to an upper or lower case everywhere to avoid problems.

It looks weird to mutate data in a validator, but I found this is the best way. I belive I shouldn't use beforeSave() callback to set the binary value for generating SQL, and return the text value back in afterSave() - supporting this code would be a classic hell like #define true false;.

Step 4. Define the mapping for output

public function fields()
{
    $fields = parent::fields();
    $fields['id'] =function(){return $this->getId();};
    return $fields;
}

This method is used by RESTful serializers to format data when you access your API with GET /video requests.

So, now you can go the generic MySQL way

Step 5. add a virtual column

ALTER TABLE t1 ADD id_text varchar(36) generated always as
 (insert(
    insert(
      insert(
        insert(hex(id_bin),9,0,'-'),
        14,0,'-'),
      19,0,'-'),
    24,0,'-')
 ) virtual;

Step 5. Use Object Relation Mapping in Yii 3 when it's available and write mapping instead of these hacks.

P.S. A couple of helper functions.

declare(strict_types=1);

namespace common\helpers;


class UUIDHelper
{
    const UUID_REGEXP = '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';

    public static function string2bin(string $uuid): string
    {
        return pack("H*", str_replace('-', '', $uuid));
    }

    public static function bin2string(string $binary): string
    {
        return strtolower(join("-", unpack("H8time_low/H4time_mid/H4time_hi/H4clock_seq_hi/H12clock_seq_low", $binary)));
    }

    public static function isUUID(string $uuid): bool
    {
        return (bool)preg_match(self::UUID_REGEXP,$uuid);
    }
}
]]>
0
[wiki] Yii v2 snippet guide Wed, 26 Aug 2020 22:45:49 +0000 https://www.yiiframework.com/wiki/2552/yii-v2-snippet-guide https://www.yiiframework.com/wiki/2552/yii-v2-snippet-guide rackycz rackycz
  1. Intro
  2. Prerequisities
  3. Yii demo app + GitLab
  4. Automatical copying from GitLab to FTP
  5. User management + DB creation + login via DB
  6. i18n translations
  7. Switching languages + session + lang-dropdown in the top menu
  8. Formatting values based on your Locale
  9. Simple access rights
  10. Nice URLs
  11. How to redirect web to subfolder /web
  12. Auto redirection from login to desired URL
  13. What to change when exporting to the Internet
  14. Saving contact inqueries into DB
  15. Tests - unit + opa
  16. Adding a google-like calendar
  17. Scenarios - UNKNOWN SCENARIO EXCEPTION
  18. Richtext / wysiwyg HTML editor - Summernote
  19. SEO optimization
  20. Other useful links
  21. jQuery + draggable/droppable on mobile devices (Android)
  22. Enhancing Gii
  23. Webproject outsite docroot (htdocs) folder (Windows)
  24. Modal window + ajax
  25. Simple Bootstrap themes
  26. Yii2 + Composer
  27. Favicon
  28. GridView + DatePicker in filter + filter reset
  29. Drop down list for foreign-key column
  30. GridView - Variable page size
  31. Creating your new helper class
  32. Form-grid renderer
  33. Netbeans + Xdebug
  34. PDF - no UTF, only English chars
  35. PDF - UTF, all chars
  36. Export (not only GridView) to CSV in UTF-8 without extensions
  37. Next chapters had to be moved to a new article!

Intro

Hi all!

This article had to be split as I reached the max length. Second part is here:

This snippet guide works with the basic Yii demo application and enhances it. It continues in my series of simple Yii tutorials. Previous two contain basic info about MVC concept, exporting to Excel and other topics so read them as well, but they are meant for Yii v1. I started with them cca in year 2011:

... and today I am beginning with Yii 2 so I will also gather my snippets and publish them here so we all can quickly setup the yii-basic-demo just by copying and pasting. This is my goal - to show how-to without long descriptions.

I was suprised that the Yii 2 demo application does not contain some basic functionalities (like login via DB, translations etc) which must be implemented in the most of web projects so I will focus on them. Plus I will talk about GitLab.

If you find any problems in my snippets, let me know, please.

Prerequisities

Skip this paragraph if you know how to run your Yii demo project...

I work with Win10 + XAMPP Server so I will expect this configuration. Do not forget to start the server and enable Apache + MySQL in the dialog. Then test that following 2 URLs work for you

You should also download the Yii basic demo application and place it into the htdocs folder. In my case it is here:

  • C:\xampp\htdocs

And your index.php should be here:

  • C:\xampp\htdocs\basic\web\index.php

If you set things correctly up, following URL will open your demo application. Now it will probably throw an exception:

The Exception is removed by entering any text into attribute 'cookieValidationKey' in file:

  • C:\xampp\htdocs\basic\config\web.php

Dont forget to connect Yii to the DB. It is done in file:

  • C:\xampp\htdocs\basic\config\db.php

... but it should work out-of-the-box if you use DB name "yii2basic" which is also used in examples below ...

.

.

Yii demo app + GitLab

Once you download and run the basic app, I recommend to push it into GitLab. You will probably need a SSH certificate which can be generated like this using PuTTYgen. When I work with Git I use TortoiseGIT which integrates all git functionalities into the context menu in Windows File Explorer.

First go to GitLab web and create a new project. Then you might need to fight a bit, because the process of connecting your PC to GIT seems to be quite complicated. At least for me.

Once things work, just create an empty folder, right click it and select Git Clone. Enter your git path, best is this format:

Note: What works for me the best is using the following command to clone my project and system asks me for the password. Other means of connection usually refuse me. Then I can start using TortoiseGIT.

git clone https://{username}@gitlab.com/{username}/{myProjectName}.git

When cloned, copy the content of the "basic" folder into the new empty git-folder and push everything except for folder "vendor". (It contains 75MB and 7000 files so you dont want to have it in GIT)

Then you can start to modify you project, for example based on this "tutorial".

Thanks to .gitignore files only 115 files are uploaded. Te vendor-folder can be recreated using command composer install which only needs file composer.json to exist.

Automatical copying from GitLab to FTP

I found these two pages where things are explained: link link and I just copied something.

You only need to create 1 file in your GitLab repository. It is named .gitlab-ci.yml and it should contain following code:

variables:
  HOST: "ftp url"
  USERNAME: "user"
  PASSWORD: "password"
  TARGETFOLDER: "relative path if needed, or just ./"

deploy:
  script:
    - apt-get update -qq && apt-get install -y -qq lftp
    - lftp -c "set ftp:ssl-allow no; open -u $USERNAME,$PASSWORD $HOST; mirror -Rnev ./ $TARGETFOLDER --ignore-time --parallel=10 --exclude-glob .git* --exclude .git/ --exclude vendor --exclude web/assets --exclude web/index.php --exclude web/index-test.php" 
  only:
    - master

I just added some exclusions (listed below) and will probably add --delete in the future. Read linked webs.

  • exclude vendor = huge folder with 3rd party SW which is not in GIT
  • exclude web/assets = also some cache
  • exclude web/index.php = in GIT is your devel index with DEBUG mode enabled. You dont wanna have this file in productive environment
  • exclude web/index-test.php = tests are only on your computer and in GIT

.

.

User management + DB creation + login via DB

To create DB with users, use following command. I recommend charset utf8_unicode_ci (or utf8mb4_unicode_ci) as it allows you to use more international characters.

CREATE DATABASE IF NOT EXISTS `yii2basic` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

USE `yii2basic`;

CREATE TABLE IF NOT EXISTS `user` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(45) NOT NULL,
  `password` VARCHAR(60) NOT NULL,
  `email`    VARCHAR(60) NOT NULL,
  `authKey`  VARCHAR(60),
  PRIMARY KEY (`id`))
ENGINE = InnoDB;

INSERT INTO `user` (`id`, `username`, `password`, `email`, `authKey`) VALUES (NULL, 'user01', '0497fe4d674fe37194a6fcb08913e596ef6a307f', 'user01@gmail.com', NULL);

If you must use MyISAM instead of InnoDB, just change the word InnoDB into MYISAM.

Then replace existing model User with following snippet

  • The model was generated by Gii and originally had 3 methods: tableName(), rules(), attributeLabels()
  • In order to use the DB for login, we needed to implement IdentityInterface which requires 5 new methods.
  • Plus we add 2 methods because of the default LoginForm and 1 validator.
<?php

namespace app\models;

use Yii;

class User extends \yii\db\ActiveRecord implements \yii\web\IdentityInterface {

    // When user detail is being edited we will only modify attribute password_new
    // Why? We dont want to load password-hash from DB and display it to the user
    // We only want him to see empty field and if it is filled in, password is changed on background
    public $password_new;
    public $password_new_repeat;

    // Use this scenario in UserController->actionCreate() right after: $model = new User() like this:
    // $model->scenario = User::SCENARIO_CREATE;
    // This will force the user to enter the password when new user is created
    // When user is edited, new password is not needed
    const SCENARIO_CREATE = "user-create";

    // ----- Default 3 model-methods by GII:

    public static function tableName() {
        return 'user';
    }

    public function rules() {
        return [
            [['username', 'email'], 'required'],
            [['password_new_repeat', 'password_new'], 'required', "on" => self::SCENARIO_CREATE],
            [['username', 'email'], 'string', 'max' => 45],
            ['email', 'email'],
            [['password', 'authKey'], 'string', 'max' => 60],
            [['password', 'password_new_repeat', 'password_new'], 'safe'],
            ['password_new_repeat', 'compare', 'operator' => '==', 'compareAttribute' => 'password_new'],
            ['password_new', 'compare', 'operator' => '==', 'compareAttribute' => 'password_new_repeat'],
            
            ['password_new_repeat', 'setPasswordWhenChanged'],
        ];
    }

    public function attributeLabels() {
        return [
            'id' => Yii::t('app', 'ID'),
            'username' => Yii::t('app', 'Username'),
            'password' => Yii::t('app', 'Password'),
            'password_new' => Yii::t('app', 'New password'),
            'password_new_repeat' => Yii::t('app', 'Repeat new password'),
            'authKey' => Yii::t('app', 'Auth Key'),
            'email' => Yii::t('app', 'Email'),
        ];
    }

    // ----- Password validator

    public function setPasswordWhenChanged($attribute_name, $params) {

        if (trim($this->password_new_repeat) === "") {
            return true;
        }

        if ($this->password_new_repeat === $this->password_new) {
            $this->password = sha1($this->password_new_repeat);
        }

        return true;
    }

    // ----- IdentityInterface methods:

    public static function findIdentity($id) {
        return static::findOne($id);
    }

    public static function findIdentityByAccessToken($token, $type = null) {
        return static::findOne(['access_token' => $token]);
    }

    public function getId() {
        return $this->id;
    }

    public function getAuthKey() {
        return $this->authKey;
    }

    public function validateAuthKey($authKey) {
        return $this->authKey === $authKey;
    }

    // ----- Because of default LoginForm:

    public static function findByUsername($username) {
        return static::findOne(['username' => $username]);
    }

    public function validatePassword($password) {
        return $this->password === sha1($password);
    }

}

Validators vs JavaScript:

  • There are 2 types of validators. All of them are used in method rules, but as you can see, the validator setPasswordWhenChanged is my custom validator and needs a special method. (I just abused a validator to set the password value, no real validation happens inside)
  • If a validator does not need this special method, it is automatically converted into JavaScript and is used on the web page when you are typing.
  • If a validator needs the method, it cannot be converted into JavaScript so the rule is checked only in the moment when user sends the form to the server - after successful JavaScript validation.

Now you can also create CRUD for the User model using GII:

CRUD = Create Read Update Delete = views and controller. On the GII page enter following values:

  • Model Class = app\models\User
  • Search Model Class = app\models\UserSearch
  • Controller Class = app\controllers\UserController
  • View Path can be empty or you can set: views\user
  • Again enable i18n

And then you can edit users on this URL: http://localhost/basic/web/index.php?r=user ... but it is not all. You have to modify the view-files so that correct input fields are displayed!

Open folder views\user and do following:

  • _form.php - rename input password to password_new then duplicate it and rename to password_new_repeat. Remove authKey.
  • _search.php - remove password and authKey.
  • index.php - remove password and authKey.
  • view.php - remove password and authKey.

Plus do not forget to use the new scenario in UserController->actionCreate() like this:

public function actionCreate()
{
  $model = new User();
  $model->scenario = User::SCENARIO_CREATE; // the new scenario!
  // ...

.

.

i18n translations

Translations are fairly simple, but I probably didnt read manuals carefully so it took me some time. Note that now I am only describing translations which are saved in files. I do not use DB translations yet. Maybe later.

1 - Translating short texts and captions

First create following folders and file.

  • "C:\xampp\htdocs\basic\messages\cs-CZ\app.php"

(Note that cs-CZ is for Czech Lanuage. For German you should use de-DE etc. Use any other language if you want.)

The idea behind is that in the code there are used only English texts and if you want to change from English to some other language this file will be used.

Now go to file config/web.php, find section "components" and paste the i18n section:

    'components' => [
        'i18n' => [
          'translations' => [
            '*' => [
              'class' => 'yii\i18n\PhpMessageSource',
              'basePath' => '@app/messages',
              'sourceLanguage' => 'en-US',
              'fileMap' => [
                'app' => 'app.php'
              ],
            ],
          ],
        ], // end of 'i18n'

        // ... other configurations

    ], // end of 'components'
    

Explanation of the asterisk * can be found in article https://www.yiiframework.com/doc/guide/2.0/en/tutorial-i18n

You surely saw that in views and models there are translated-texts saved like this:

Yii::t('app', 'New password'),

It means that this text belongs to category "app" and its English version (and also its ID) is "New password". So this ID will be searched in the file you just created. In my case it was the Czech file:

  • "C:\xampp\htdocs\basic\messages\cs-CZ\app.php"

Therefore open the file and paste there following code:

<?php
return [
    'New password' => 'Nové heslo',
];
?>

Now you can open the page for adding a new user and you will see than so far nothing changed :-)

We must change the language ... For now let's do it in a primitive and permanent way again in file config/web.php

$config = [
    // use your language
    // also accessible via Yii::$app->language
    'language' => 'cs-CZ',
    
    // This attribute is not necessary.
    // en-US is default value
    'sourceLanguage' => 'en-US',
    
    // ... other configs

2 - Translating long texts and whole views

If you have a view with long texts and you want to translate it into a 2nd language, it is not good idea to use the previous approach, because it uses the English text as the ID.

It is better to translate the whole view. How? ... Just create a sub-folder next to the view and give it name which will be identical to the target-lang-ID. In my case the 2nd language is Czech so I created following folder and copied my view in it. So now I have 2 identical views with identical names:

  • "C:\xampp\htdocs\basic\views\site\about.php" ... English
  • "C:\xampp\htdocs\basic\views\site\cs-CZ\about.php" ... Czech

Yii will automatically use the Czech version if needed.

.

.

Switching languages + session + lang-dropdown in the top menu

First lets add to file config/params.php attributes with list of supported languages:

<?php
return [
    // ...
    'allowedLanguages' => [
        'en-US' => "English",
        'cs-CZ' => "Česky",
    ],
    'langSwitchUrl' => '/site/set-lang',
];

This list can be displayed in the main menu. Edit file:

  • C:\xampp\htdocs\basic\views\layouts\main.php

And above the Nav::widget add few rows:

    $listOfLanguages = [];
    $langSwitchUrl = Yii::$app->params["langSwitchUrl"];
    foreach (Yii::$app->params["allowedLanguages"] as $langId => $langName) {
        $listOfLanguages[] = ['label' => Yii::t('app', $langName), 'url' => [$langSwitchUrl, 'langID' => $langId]];
    }

and then add one item into Nav::widge

    echo Nav::widget([
        // ...
        'items' => [
            // ...
            ['label' => Yii::t('app', 'Language'),'items' => $listOfLanguages],
            // ...

Now in the top-right corner you can see a new drop-down-list with list of 2 languages. If one is selected, action "site/setLang" is called so we have to create it in SiteController.

Note that this approach will always redirect user to the new action and his work will be lost. Nevertheless this approach is very simple so I am using it in small projects. More complex projects may require an ajax call when language is changed and then updating texts using javascript so reload is not needed and user's work is preserved. But I expect that when someone opens the web, he/she sets the language immediately and then there is no need for further changes.

The setLang action looks like this:

    public function actionSetLang($langID = "") {
        $allowedLanguages = Yii::$app->params["allowedLanguages"];
        $langID = trim($langID);
        if ($langID !== "" && array_key_exists($langID, $allowedLanguages)) {
            Yii::$app->session->set('langID', $langID);
        }
        return $this->redirect(['site/index']);
    }

As you can see when the language is changed, redirection to site/index happens. Also mind that we are not modifying the attribute from config/web.php using Yii::$app->language, but we are saving the value into the session. The reason is that PHP deletes memory after every click, only session is kept.

We then can use the langID-value in other controllers using new method beforeAction:

    public function beforeAction($action) {

        if (!parent::beforeAction($action)) {
            return false;
        }

        Yii::$app->language = Yii::$app->session->get('langID');

        return true;
    }

.. or you can create one parent-controller named for example BaseController. All other controllers will extend it.

<?php

namespace app\controllers;

use Yii;
use yii\web\Controller;

class BaseController extends Controller {

    public function beforeAction($action) {

        if (!parent::beforeAction($action)) {
            return false;
        }

        Yii::$app->language = Yii::$app->session->get('langID');

        return true;
    }

}

As you can see in the snippet above, other controllers must contain row "use app\controllers\BaseController" + "extends BaseController".

Formatting values based on your Locale

Go to config\web.php and add following values:

$config = [
  // ..
 'language' => 'cs-CZ', 
 // \Yii::$app->language: 
 // https://www.yiiframework.com/doc/api/2.0/yii-base-application#$language-detail
//..
 'components' => [
  'formatter' => [
   //'locale' => 'cs_CZ', 
   // Only effective when the "PHP intl extension" is installed else "language" above is used: 
   // https://www.php.net/manual/en/book.intl.php

   //'language' => 'cs-CZ', 
   // If not set, "locale" above will be used:
   // https://www.yiiframework.com/doc/api/2.0/yii-i18n-formatter#$language-detail
      
   // Following values might be usefull for your situation:
   'booleanFormat' => ['Ne', 'Ano'],
   'dateFormat' => 'yyyy-mm-dd', // or 'php:Y-m-d'
   'datetimeFormat' => 'yyyy-mm-dd HH:mm:ss', // or 'php:Y-m-d H:i:s'
   'decimalSeparator' => ',',
   'defaultTimeZone' => 'Europe/Prague',
   'thousandSeparator' => ' ',
   'timeFormat' => 'php:H:i:s', //  or HH:mm:ss
   'currencyCode' => 'CZK',
  ],

In GridView and DetailView you can then use following and your settings from above will be used:

'columns' => [
 [
  'attribute' => 'colName',
  'label' => 'Value',
  'format'=>['decimal',2]
 ],
 [
   'label' => 'Value', 
   'value'=> function ($model) { return \Yii::$app->formatter->asDecimal($model->myCol, 2) . ' EUR' ; } ],
 ]
 // ...
]

PS: I do not use currency formatter as it always puts the currency name before the number. For example USD 123. But in my country we use format: 123 CZK.

More links on this topic:

Simple access rights

Every controller can allow different users/guests to use different actions. Method behaviors() can be used to do this. If you generate the controller using GII the method will be present and you will just add the "access-part" like this:


// don't forget to add this import:
use yii\filters\AccessControl;

public function behaviors() {
  return [
    // ...
    'access' => [
      'class' => AccessControl::className(),
      'rules' => [
        [
          'allow' => true,
          'roles' => ['@'], // logged in users
          // 'roles' => ['?'], // guests
          // 'matchCallback' => function ($rule, $action) {
            // all logged in users are redirected to some other page
            // just for demonstration of matchCallback
            // return $this->redirect('index.php?r=user/create');
          // }
        ],
      ],
      // All guests are redirected to site/index in current controller:
      'denyCallback' => function($rule, $action) {
        Yii::$app->response->redirect(['site/index']);
      },
    ],
  ];
}

.. This is all I needed so far. I will add more complex snippet as soon as I need it ...

Details can be found here https://www.yiiframework.com/doc/guide/2.0/en/security-authorization.

.

.

Nice URLs

Just uncomment section "urlManager" in config/web.php .. htaccess file is already included in the basic demo. In case of problems see this link.

My problem was that images were not displayed when I enabled nice URLs. Smilar discussion here.

// Originally I used these img-paths:
<img src="..\web\imgs\myimg01.jpg"/>

/// Then I had to chage them to this:
Html::img(Yii::$app->request->baseUrl . '/imgs/myimg01.jpg')

// The important change is using the "baseUrl"

Note that Yii::$app->request->baseUrl returns "/myProject/web". No trailing slash.

.

.

How to redirect web to subfolder /web

Note: If you are using the advanced demo app, this link can be interesting for you.

Yii 2 has the speciality that index.php is hidden in the web folder. I didnt find in the official documentation the important info - how to hide the folder, because user is not interested in it ...

Our demo application is placed in folder:

  • C:\xampp\htdocs\basic\web\index.php

Now you will need 2 files named .htaccess

  • C:\xampp\htdocs\basic\web\.htaccess
  • C:\xampp\htdocs\basic\.htaccess

The first one is mentioned in chapter Nice URLs and looks like this:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

The second is simpler:

RewriteEngine on
RewriteRule ^(.*)$ web/$1 [L]

... it only adds the word "web" into all URLs. But first we have to remove the word from URLs. Open file config/web.php and find section request. Add attribute baseUrl:

'request' => [
  // 'cookieValidationKey' => ...
  'baseUrl' => '/basic', // add this line
],

Now things will work for you. But it might be needed to use different value for devel and productive environment. Productive web is usually in the root-folder so baseUrl should be en empty string. I did it like this:

$baseUrlWithoutWebFolder = "";
if (YII_ENV_DEV) {
  $baseUrlWithoutWebFolder = '/basic';
}

// ...

'request' => [
  // 'cookieValidationKey' => ...
  'baseUrl' => $baseUrlWithoutWebFolder,
],

I will test this and if I find problems and solutions I will add them.

.

.

Auto redirection from login to desired URL

... to be added ...

.

.

What to change when exporting to the Internet

  • Delete file web/index-test.php
  • In file web/index.php comment you 2 first lines containing YII_DEBUG + YII_ENV
  • Delete the text from view site/login which says "You may login with admin/admin or demo/demo."

.

.

Saving contact inqueries into DB

DROP TABLE IF EXISTS `contact` ;

CREATE TABLE IF NOT EXISTS `contact` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `email` VARCHAR(45) NOT NULL,
  `subject` VARCHAR(100) NOT NULL,
  `body` TEXT NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;
  • Create the DB table
  • Generate Model + CRUD using GII
  • In Site controller replace ContactForm with Contact (in section "use" and in actionContact) and in the action change the IF condition:
    use app\models\Contact;
    // ... 
    public function actionContact() {
      $model = new Contact();
      if ($model->load(Yii::$app->request->post()) && $model->save()) {
      // ...
    
  • Open the new contact model and add one attribute and 2 rules:
public $verifyCode;
// ...
  ['verifyCode', 'captcha'],
  ['email', 'email'],

// and translation for Captcha
'verifyCode' => Yii::t('app', 'Verification'),
  • You can also delete one paragraph from view/site/contact
    <p>
    Note that if you turn on the Yii debugger ...
    

Then some security - filtering users in the new ContactController:

public function beforeAction($action) {

  if (!parent::beforeAction($action)) {
    return false;
  }

  $guestAllowedActions = [];

  if (Yii::$app->user->isGuest) {
    if (!in_array($action->actionMethod, $guestAllowedActions)) {
      return $this->redirect(['site/index']);
    }
  }
  
  return true;
}

.

.

Tests - unit + opa

... text ...

.

.

Adding a google-like calendar

I needed to show user a list of his events in a large calendar so I used library fullcalendar.

Great demo which you can just copy and paste:

/*I added this style to hide vertical scroll-bars*/
.fc-scroller.fc-day-grid-container{
  overflow: hidden !important;
}
  • Don't forget to use these files for example in your view like this:
$this->registerCssFile('@web/css/fullcalendar/fullcalendar.css');
$this->registerCssFile('@web/css/fullcalendar/fullcalendar.print.css', ['media' => 'print']); 

$this->registerJsFile('@web/js/fullcalendar/moment.min.js', ['depends' => ['yii\web\JqueryAsset']]);
$this->registerJsFile('@web/js/fullcalendar/fullcalendar.min.js', ['depends' => ['yii\web\JqueryAsset']]);

// details here:
// https://www.yiiframework.com/doc/api/2.0/yii-web-view

... if you want to go pro, use NPM. The NPM way is described here.

API is here: https://fullcalendar.io/docs ... you can then enhace the calendar config from the example above

In order to make things work I had to force jQuery to be loaded before calendar scripts using file config/web.php like this

   'components' => [
        
		// ...
		
       'assetManager' => [
            'bundles' => [
                'yii\web\JqueryAsset' => [
                    'jsOptions' => [ 'position' => \yii\web\View::POS_HEAD ],
                ],
            ],
        ],

You can customize the calendar in many ways. For example different event-color is shown here. Check the source code.

.

.

Scenarios - UNKNOWN SCENARIO EXCEPTION

I have been using scenarios a lot but today I spent 1 hour on a problem - I had 2 scenarios and one of them was just assigned to the model ...

$model->scenario = "abc";

... but had no rule defined yet. I wanted to implement the rule later, but I didnt know that when you set a scenario to your model it must be used in method rules() or defined in method scenarios(). So take this into consideration. I expected that when the scenario has no rules it will just be skipped or deleted.

.

.

Richtext / wysiwyg HTML editor - Summernote

If you want to allow user to enter html-formatted text, you need to use some HTML wysiwyg editor, because ordinary TextArea can only work with plain text. It seems to me that Summernote is the simplest addon available:

// Add following code to file layouts/main.php .. 
// But make sure jquery is already loaded !! 
// - Read about this topic in chapter "Adding a google-like calendar"

<!-- include summernote css/js -->
<link href="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.css" rel="stylesheet">
<script src="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.js"></script>

// And then in any view you can use this code:

<script>
$(document).ready(function() {
  $('#summernote1').summernote();
  $('#summernote2').summernote();
});
</script>
<div id="summernote1">Hello Summernote</div>

<form method="post">
  <textarea id="summernote2" name="editordata"></textarea>
</form>

On this page I showed how to save Contacts inqueries into database. If you want to use the richtext editor in this section, open view contact/_form.php and just add this JS code:

<script>
$(document).ready(function() {
  $('#contact-body').summernote();
});
</script>

It will be saved to DB as HTML code. But this might be also a source of problems, because user can inject some dangerous HTML code. So keep this in mind.

Now you will also have to modify view contact/view.php like this in order to see nice formatted text:

DetailView::widget([
  'model' => $model,
  'attributes' => [
    // ...
    'body:html',
  ],
])

... to discover all possible formatters, check all asXXX() functions on this page:

.

.

SEO optimization

This is not really a YII topic but as my article is some kind of a code-library I will paste it here as well. To test your SEO score you can use special webs. For example seotesteronline, but only once per day. It will show some statistics and recommend enhancements so that your web is nicely shown on FB and Twitter or found by Google.

Important are for example OG meta tags or TWITTER meta tags. They are basicly the same. Read more here. You can test them at iframely.com.

Basic tags are following and you should place them to head:

  • Note that Twitter is using attribute "name" instead of "property" which is defined in OG
  • btw OG was introduced by Facebook. Twitter can process it as well, but SEO optimizers will report an error when Twitter's tags are missing.

<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>

  <meta property="og:site_name" content="European Travel, Inc.">
  <meta property="og:title" content="European Travel Destinations">
  <meta property="og:description" content="Offering tour packages for individuals or groups.">
  <meta property="og:image" content="http://euro-travel-example.com/thumbnail.jpg">
  <meta property="og:url" content="http://euro-travel-example.com/index.htm">
  <meta name="twitter:card" content="summary_large_image">

  <!--  Non-Essential, But Recommended -->
  <meta property="og:site_name" content="European Travel, Inc.">
  <meta name="twitter:image:alt" content="Alt text for image">

  <!--  Non-Essential, But Required for Analytics -->
  <meta property="fb:app_id" content="your_app_id" />
  <meta name="twitter:site" content="@website-username">
  
  <!-- seotesteronline.com will also want you to add these: -->
  <meta name="description" content="blah blah">
  <meta property="og:type" content="website">
  <meta name="twitter:title" content="blah blah">
  <meta name="twitter:description" content="blah blah">
  <meta name="twitter:image" content="http://something.jpg">

Do not forget about file robots.txt and sitemap.xml:

// robots.txt can contain this:
User-agent: *
Allow: /

Sitemap: http://www.example.com/sitemap.xml
// And file sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
  <url>
    <loc>http://example.com/someFile.html</loc>
    <image:image>
      <image:loc>http://example.com/someImg.jpg</image:loc>
    </image:image>
  </url> 
</urlset> 

You can also minify here or here all your files. Adding "microdata" can help as well, but I have never used it. On the other hand what I do is that I compress images using these two sites tinyjpg.com and tinypng.com.

.

.

Other useful links

.

.

jQuery + draggable/droppable on mobile devices (Android)

JQuery and its UI extension provide drag&drop functionalities, but these do not work on Android or generally on mobile devices. You can use one more dependency called touch-punch to fix the problem. It should be loaded after jQuery and UI.

<!-- jQuery + UI -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

<!-- http://touchpunch.furf.com/ -->
<!-- Use this file locally -->
<script src="./jquery.ui.touch-punch.min.js"></script>

And then standard code should work:

<!doctype html>

<html lang="en">
  <head>
    <meta charset="utf-8">

    <title>Title</title>

    <!-- jQuery + UI -->
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

    <!-- http://touchpunch.furf.com/ -->
    <script src="./jquery.ui.touch-punch.min.js"></script>

    <style>
      .draggable {
        width: 100px;
        height: 100px;
        border: 1px solid red;
      }

      .droppable {
        width: 300px;
        height: 300px;
        border: 1px solid blue;
      }

      .over {
        background-color: gold;
      }
    </style>
  </head>

  <body>
    <div class="draggable my1">draggable my1</div>
    <div class="draggable my2">draggable my2</div>
    <div class="droppable myA">droppable myA</div>
    <div class="droppable myB">droppable myB</div>
  </body>


  <script>
    $( function() {

      // All draggables will return to their original position if not dropped to correct droppable
      // ... and will always stay in the area of BODY
      $(".draggable").draggable({ revert: "invalid", containment: "body" });

      // Demonstration of how particular droppables can accept only particular draggables
      $( ".droppable.myA" ).droppable({
        accept: ".draggable.my1",
        drop: function( event, ui ) {

          // positioning the dropped box into the target area
          var dropped = ui.draggable;
          var droppedOn = $(this);
          $(dropped).detach().css({top: 0,left: 0}).appendTo(droppedOn);    
          $(this).removeClass("over");
        },
        over: function(event, elem) {
          $(this).addClass("over");
          console.log("over");
        },
        out: function(event, elem) {
          $(this).removeClass("over");
        }
      });

      // Demonstration of how particular droppables can accept only particular draggables
      $( ".droppable.myB" ).droppable({
        accept: ".draggable.my2",
        drop: function( event, ui ) {

          // positioning the dropped box into the target area
          var dropped = ui.draggable;
          var droppedOn = $(this);
          $(dropped).detach().css({top: 0,left: 0}).appendTo(droppedOn);    
          $(this).removeClass("over");
        },
        over: function(event, elem) {
          $(this).addClass("over");
          console.log("over");
        },
        out: function(event, elem) {
          $(this).removeClass("over");
        }
      });

    });
  </script>

</html>

.

.

Enhancing Gii

If you do not like entering long model-paths and controller-paths in CRUD-generator, you can modify text boxes in "\vendor\yiisoft\yii2-gii\src\generators\crud\form.php" and enter default paths and then only manually add the name of the model.

if (!$generator->modelClass) {
	echo $form->field($generator, 'modelClass')->textInput(['value' => 'app\\models\\']);
	echo $form->field($generator, 'searchModelClass')->textInput(['value' => 'app\\models\\*Search']);
	echo $form->field($generator, 'controllerClass')->textInput(['value' => 'app\\controllers\\*Controller']);	
} else {
	echo $form->field($generator, 'modelClass');
	echo $form->field($generator, 'searchModelClass');
	echo $form->field($generator, 'controllerClass');
}

.

.

Webproject outsite docroot (htdocs) folder (Windows)

If you need to store you project for example in folder D:\GIT\EmployerNr1\ProjectNr2, you can. Just modify 2 files and restart Apache (I am using XAMPP under Win):

  • C:\Windows\System32\drivers\etc\hosts
127.0.0.1 myFictiveUrl.local
  • C:\xampp\apache\conf\extra\httpd-vhosts.conf
<VirtualHost *:80>
  DocumentRoot "D:\GIT\EmployerNr1\ProjectNr2"
  ServerName myFictiveUrl.local
  ServerAlias myFictiveUrl.local
  <Directory "D:\GIT\EmployerNr1\ProjectNr2">
    Options Indexes FollowSymLinks
    AllowOverride All
    Order allow,deny
    Allow from all
    # New directive needed in Apache 2.4.3:
    Require all granted
  </Directory>
</VirtualHost>

You can then use http://myFictiveUrl.local in your browser

.

.

Modal window + ajax

Let's have a GridView (list of users) with edit-button which will open the edit-form in a modal window. Once user-detail is changed, ajax validation will be executed. If something is wrong, the field will be highlighted. If everything is OK and saved, modal window will be closed and the GridView will be updated.

Let's add the button to the GridView in the view index.php and let's wrap the GridView into the Pjax. Also ID is added to the GridView so it can be refreshed later via JS:

<?php yii\widgets\Pjax::begin();?>
<?= GridView::widget([
  'dataProvider' => $dataProvider,
  'filterModel' => $searchModel,
  'id' => 'user-list-GridView',
  'columns' => [
    ['class' => 'yii\grid\SerialColumn'],
      'id',
      'username',
      'email:email',
      ['class' => 'yii\grid\ActionColumn',
        'buttons' => [
          'user_ajax_update_btn' => function ($url, $model, $key) {
            return Html::a ( '<span class="glyphicon glyphicon-share" aria-hidden="true"></span> ', 
			  ['user/update', 'id' =>  $model->id], 
			  ['class' => 'openInModal', 'data-modal-titl' => 'Some text'] 
		    );
          },
        ],
        'template' => '{update} {view} {delete} {user_ajax_update_btn}'
      ],
  ],
]); ?>
<?php yii\widgets\Pjax::end();?>

Plus add (to the end of this view) following JS code:

<?php
// This section can be moved to "\views\layouts\main.php"
yii\bootstrap\Modal::begin([
  'header' => '<span id="modalTitle">Title</span>',
  'id' => 'modalDialog1',
  'size' => 'modal-lg',
]);
echo "<div id='modalContent'></div>";
yii\bootstrap\Modal::end();

$this->registerJs(
  "$('a.openInModal').click(function(e){  
  e.preventDefault();
  $('#modalDialog1 #modalTitle').text('aaa');
  $('#modalDialog1').modal('show')
    .find('#modalContent')
    .load($(this).attr('href'));
  return false;
  });",
  yii\web\View::POS_READY,
  'modalHandler'
);
?>

Now we need to modify the updateAction:

public function actionUpdate($id)
{
  $model = $this->findModel($id);

  if ($model->load(Yii::$app->request->post()) && $model->save()) {
    if (Yii::$app->request->isAjax) {
      return "<script>"
        . "$.pjax.reload({container:'#user-list-GridView'});"
        . "$('#modalDialog1').modal('hide');"
        . "</script>";
    }

    return $this->redirect(['view', 'id' => $model->id]);
  }

  if (Yii::$app->request->isAjax) {
    return $this->renderAjax('update', [
      'model' => $model,
    ]);
  }
    
  return $this->render('update', [
        'model' => $model,
  ]);
}

And file _form.php:

<?php yii\widgets\Pjax::begin([
  'id' => 'user-detail-Pjax', 
  'enablePushState' => false, 
  'enableReplaceState' => false
]);  ?>

<?php $form = ActiveForm::begin([
  'id'=>'user-detail-ActiveForm',
  'options' => ['data-pjax' => 1 ]
  ]); ?>

<?= $form->field($model, 'username')->textInput(['maxlength' => true]) ?>

<?= $form->field($model, 'password')->passwordInput(['maxlength' => true]) ?>

<?= $form->field($model, 'email')->textInput(['maxlength' => true]) ?>

<?= $form->field($model, 'authKey')->textInput(['maxlength' => true]) ?>

<div class="form-group">
    <?= Html::submitButton(Yii::t('app', 'Save'), ['class' => 'btn btn-success']) ?>
</div>

<?php ActiveForm::end(); ?>

<?php yii\widgets\Pjax::end() ?>

Simple Bootstrap themes

There is this page bootswatch.com which provides simple bootstrap themes. It is enough to replace one CSS file - you can do it in file "views/layouts/main.php" just by adding following row before < /head > tag:

<link href="https://bootswatch.com/3/united/bootstrap.min.css" rel="stylesheet">

</head>

Note that currently Yii2 is using Bootstrap3 so when searching for themes, dont forget to switch to section Bootstrap 3.

Important: Yii2 is using navbar with classes "navbar-inverse navbar-fixed-top". If you are using themes from Bootswatch, change the navbar class to "navbar navbar-default navbar-fixed-top" otherwise the top menu-bar will have weird color. This is also done in file "views/layouts/main.php" like this:

    NavBar::begin([
        // ...
        'options' => [
            'class' => 'navbar navbar-default navbar-fixed-top',
        ],
    ]);

Note: If you want to download the theme, you should link it like this:

<link href="<?=Yii::$app->getUrlManager()->getBaseUrl()?>/css/bootstrap-bootswatch-united.min.css" rel="stylesheet">

Now you technically do not need the original bootstrap.css file so you can remove it in "basic/config/web.php" by adding the assetManager section to "components":

'components' => [
  // https://stackoverflow.com/questions/26734385/yii2-disable-bootstrap-js-jquery-and-css
  'assetManager' => [
    'bundles' => [
	'yii\bootstrap\BootstrapAsset' => [
	  'css' => [],
	 ],
     ],
   ],

Yii2 + Composer

Once composer is installed, you might want to use it to download Yii, but following command might not work:

php composer.phar create-project yiisoft/yii2-app-basic basic

Change it to:

composer create-project yiisoft/yii2-app-basic basic

.. and run it. If you are in the desired folder right now, you can use . (dot) instead of the last "word":

composer create-project yiisoft/yii2-app-basic .

Using DatePicker

Run this command:

composer require --prefer-dist yiisoft/yii2-jui

and then use this code in your view:

<?= $form->field($model, 'date_deadline')->widget(\yii\jui\DatePicker::classname(), [
    //'language' => 'en',
    'dateFormat' => 'yyyy-MM-dd',
    'options' => ['class' => 'form-control']
]) ?>

Read more at the official documentation and on GIT

Favicon

Favicon is already included, but it nos used in the basic project. Just type this into views/layouts/main.php:

<link rel="icon" type="image/png" sizes="16x16" href="favicon.ico">

Or you can use the official yii-favicon:

<link rel="apple-touch-icon" sizes="180x180" href="https://www.yiiframework.com/favico/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://www.yiiframework.com/favico/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="https://www.yiiframework.com/favico/favicon-16x16.png">

GridView + DatePicker in filter + filter reset

If you are using DatePicker as described above, you can use it also in GridView as a filter, but it will not work properly. Current filter-value will not be visible and resetting the filter wont be possible. Use following in views/xxx/index.php to solve the issue:

function getDatepickerFilter($searchModel, $attribute) {
  $name = basename(get_class($searchModel)) . "[$attribute]";
  $result = \yii\jui\DatePicker::widget(['language' => 'en', 'dateFormat' => 'php:Y-m-d', 'name'=>$name, 'value'=>$searchModel->$attribute, 'options' => ['class' => 'form-control'] ]);
  if (trim($searchModel->$attribute)!=='') {
    $result = '<div style="display:flex;flex-direction:column">' . $result
    . '<div class="btn btn-danger btn-xs glyphicon glyphicon-remove" onclick="$(this).prev(\'input\').val(\'\').trigger(\'change\')"></div></div>';
  }	
  return $result;
}

// ...

<?= GridView::widget([
  'dataProvider' => $dataProvider,
  'filterModel' => $searchModel,
  'columns' => [
  // ...
  [
    'attribute' => 'myDateCol',
    'value' => 'myDateCol',
    'label'=>'My date label',
    'filter' => getDatepickerFilter($searchModel,'myDateCol'),
    'format' => 'html'
  ],
        
  // ...
        

Drop down list for foreign-key column

Do you need to specify for example currency using a predefined list, but your view contains only a simple text-input where you must manually enter currency_id from table Currency?

Read how to enhance it.

use yii\helpers\ArrayHelper;
use app\models\Currency; // My example uses Currency model

$currencies = Currency::find()->asArray()->all();

// 'id' = the primary key column
// 'name' = the column with text to be dispalyed to user
// https://www.yiiframework.com/doc/api/2.0/yii-helpers-basearrayhelper#map()-detail
$currencies = ArrayHelper::map($currencies, 'id', 'name'); 

<?= $form->field($model, 'id_currency')->dropDownList($currencies) ?>

Note: In other views you will need models with predefined relations to reach the correct value. Relations can be created using GII (when they are defined in DB) or manually.

GridView - Variable page size

GridView cannot display DropDownList which could be used by the user to change the number of rows per page. You have to add it manually like this:

When you are creating a new model using Gii, you can select if you want to create the SearchModel as well. Do it, it is usefull for example in this situation. Then add following rows to the model:

// file models/InvoiceSearch.php

use yii\helpers\Html; // add this row

class InvoiceSearch extends Invoice
{
  public $pageSize = null // add this row
  // ...
  
  // This method already exists:
  public function rules()
  {
    return [ // ...
      ['pageSize', 'safe'], // add this row
      // ...
  
  // Add this function:
  public function getPageSizeDropDown($htmlOptions = [], $prefixHtml = '', $suffixHtml = '', $labelPrefix = '') {
    return $prefixHtml . Html::activeDropDownList($this, 'pageSize',
      [
        10 => $labelPrefix.'10', 
        20 => $labelPrefix.'20', 
        50 => $labelPrefix.'50', 
        100 => $labelPrefix.'100', 
        150 => $labelPrefix.'150', 
        200 => $labelPrefix.'200', 
        300 => $labelPrefix.'300', 
        500 => $labelPrefix.'500', 
        1000 => $labelPrefix.'1000'
      ],$htmlOptions ) . $suffixHtml;
    }

    // Add this function:
    public function getPageSizeDropDownID($prefix = '#') {
      return $prefix . Html::getInputId($this, 'pageSize');
    }
    
    // This method already exists:
    public function search($params)
    {
        // Remember to call load() first and then you can work with pageSize
        $this->load($params);
        
        // Add following rows:
        if (!isset($this->pageSize)) {
          // Here we make sure that the dropDownLst will have correct value preselected
          $this->pageSize = $dataProvider->pagination->defaultPageSize;
        } 
        $dataProvider->pagination->pageSize = (int)$this->pageSize; 
        

And then in your views/xxx/index.php use following:

$pageSizeDropDown = $searchModel->getPageSizeDropDown(['class' => 'form-control', 'style'=>'width: 20rem'],'','','Rows per page: ');

echo GridView::widget([
  'dataProvider' => $dataProvider,
  'filterModel' => $searchModel,
  'layout'=>'{summary}<br>{items}<br><div style="display:flex; background-color: #f9f9f9; padding: 0px 3rem;"><div style="flex-grow: 2;">{pager}</div><div style="align-self:center;">'.$pageSizeDropDown.'</div></div>',
  'pager' => [ 'maxButtonCount' => 20 ],
  
  'filterSelector' => $searchModel->getPageSizeDropDownID(),
  // filterSelector is the core solution of this problem. It refreshes the grid.

Creating your new helper class

Sometimes you need a static class that will do things for you. This is what helpers do.

I work with the Basic example so I do things like this:

  • Create folder named "myHelpers" next to the folder "controllers"
  • Place there your class and do not forget about the "namespace":
<?php
namespace myHelpers;
class MyClassName { /* ... */ }
  • Now open file index.php and add following row:
require __DIR__ . '/../myHelpers/MyClassName.php';
  • If you want to use the class, do not forget to include the file:
use myHelpers\MyClassName;
// ...
echo MyClassName::myMethod(123);

Form-grid renderer

If you want your form to be rendered in a grid, you can use your custom new helper to help you. How to create helpers is mentioned right above. The helper then looks like this:

<?php
namespace myHelpers;

class GridFormRenderer {
  
  // https://www.w3schools.com/bootstrap/bootstrap_grid_system.asp
  // Bootstrap works with 12-column layouts so max span is 12.
  public static function renderGridForm($gridForm, $colSize = 'md', $nullReplacement = '&nbsp;', $maxBoootstrapColSpan = 12) {
    $result = '';
    foreach ($gridForm as $row) {
      if (is_null($row)) {
        $colSpan = $maxBoootstrapColSpan;
        $result .= '<div class="row">' . '<div class="col-' . $colSize . '-' . $colSpan . '">' . $nullReplacement . '</div></div>';
        continue;
      }
      $colSpan = floor($maxBoootstrapColSpan / count($row));
      $result .= '<div class="row">';
      foreach ($row as $col) {
        if (is_null($col)) {
          $col = $nullReplacement;
        }
        $result .= '<div class="col-' . $colSize . '-' . $colSpan . '">' . $col . '</div>';
      }
      $result .= '</div>';
    }
    return $result;
  }
}

And is used like this in any view:

use myHelpers\GridFormRenderer;
// ...

$form = ActiveForm::begin();

$username = $form->field($model, 'username')->textInput(['maxlength' => true]);
$password_new = $form->field($model, 'password_new')->passwordInput(['maxlength' => true]);
$password_new_repeat = $form->field($model, 'password_new_repeat')->passwordInput(['maxlength' => true]);
$email = $form->field($model, 'email')->textInput(['maxlength' => true]);

$gridForm = [
  [$username, null, $email], // null = empty cell
  null, // null = empty row
  [$password_new, $password_new_repeat],
  ];

echo GridFormRenderer::renderGridForm($gridForm);

ActiveForm::end();
// ...

The result is that your form has 3 rows, the middle one is empty. In the first row there are 3 cells (username, nothing, email) and in the last row there is 2x password.

You do not have to write any HTML, you only arrange inputs into any number of rows and columns (using the array $gridForm) and things just happen automagically.

Netbeans + Xdebug

Note: I am using Windows 10 + XAMPP

I had to follow 2 manuals:

The result in C:\xampp\php\php.ini was:

[XDebug]
zend_extension = c:\xampp\php\ext\php_xdebug.dll
xdebug.remote_enable = on
xdebug.idekey = netbeans-xdebug
xdebug.remote_host = localhost
xdebug.remote_port = 9000
xdebug.remote_autostart=on
xdebug.var_display_max_depth=5

The last row changes behaviour of var_dump() when xdebug is installed. It does not output whole arrays, but max 3 levels. Read here or here.

Quotes were not important. I didnt even need to download current version of xdebug, it was already in folder C:\xampp\php\ext.

Important also is to righ-click your project, select Properties, then menu "Run configurations" and set correct path to your index.php. Best is to use the button "Browse"

Then you just add a breakpoint, click the debug-play button in NetBeans and refresh your browser. Netbeans will stop the code for you.

PDF - no UTF, only English chars

For creating PDFs can be used FPDF library. It is extremely simple to make it run. Just download it and then use it as a helper - I described how this is done above. Do not forget to add namespace to FPDF.php.

You will only need FPDF.php and folder font. Then in your controller just do this:

use myHelpers\FPDF;
// ...
$pdf = new FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello World!');
$pdf->Output('D', 'hello.pdf');

Note: I renamed original file fpdf.php to FPDF.php

The only disadvantage is that UTF cannot be used and conversion to older encodings is required. For Czech Republic all texts must be converted like this:

private function convertUtf8ToWin1250($value) {
  $value = trim($value);
  if (strlen($value)==0) {
    // Warning:
    // Method strlen() returns number of bytes, not necessiraly number of characters.
    // Usually it is the same, but not always.
    // see also mb_strlen()
    return '';
  }
  return iconv("UTF-8", "WINDOWS-1250//IGNORE", $value );
}

A discussion is available here.

PDF - UTF, all chars

When you need non-English characters, tFPDF should be used. It is the same as FPDF so FPDF documentation and manual can be used. It only modifies character-handling.

Just download it and then use it as a helper - I described how this is done above.

Summary:

  • Download tFPDF and unpack it.
  • use file tfpdf.php and folder font .. it contains file ttfonts.php !!
  • Into both mentioned php files add the namespace you are using for your helpers.
  • Do other modifications needed to use it as a Helper. Explained above.

tFPDF example:

$pdf = new tFPDF();

$pdf->AddFont('DejaVu','','DejaVuSansCondensed.ttf',true);
$pdf->AddFont('DejaVu','B','DejaVuSansCondensed-Bold.ttf',true);
$pdf->SetFont('DejaVu','',10);

$pageWidth = 210;
$pageMargin = 10;
$maxContentW = $pageWidth - 2*$pageMargin; // = max width of an element

$pdf->SetAutoPageBreak(true, 0);
$pdf->SetMargins($pageMargin, $pageMargin, $pageMargin);
$pdf->SetAutoPageBreak(true, $pageMargin);

// Settings for tables:
$pdf->SetLineWidth(0.2);
$pdf->SetDrawColor(0, 0, 0);

$pdf->AddPage();
/ $pdf->SetFontSize(8);

$displayBorders = 1;
$valueAlign = "L";
$labelAlign = "L";

$usedHeight = 0;

// Logo on the 1st line
$pdf->SetY($pageMargin);
$pdf->SetX($pageMargin);
$logoPath = '../tesla.png';
$imgWidth = 10;
$headerHeight = 10;
$pdf->Image($logoPath, null, null, $imgWidth, $headerHeight);

$pdf->SetY($pageMargin);
$pdf->SetX($pageMargin+$imgWidth);
$pdf->Cell($maxContentW-$imgWidth, $headerHeight, 'Non English chars: ěščřžýáíéúů', $displayBorders, 0, 'C', false);

$usedHeight+= $headerHeight;
$usedHeight+=10;
        
$pdf->SetY($pageMargin);
$pdf->SetX($pageMargin+$imgWidth);
$pdf->Cell($maxContentW-$imgWidth, 10, 'Non English chars: ěščřžýáíéúů', $displayBorders, 0, 'C', false);

$pdf->SetY($pageMargin + $usedHeight);
$pdf->SetX($pageMargin);
$pdf->Cell(30, 10, 'Customer number:', $displayBorders, 0, 'R', false);

$pdf->SetFont('DejaVu','B',14);

$pdf->SetY($pageMargin + $usedHeight);
$pdf->SetX($pageMargin + 30);
$pdf->Cell(20, 10, 'ABC123', $displayBorders, 0, 'L', false);

$pdf->Output('D', 'hello.pdf');

Note to tFPFD: Once you use it, it creates a few PHP and DAT files in folder unifont. Delete them before uploading to the internet. They will contain hardcoded paths to fonts and must be recreated.

Export (not only GridView) to CSV in UTF-8 without extensions

I will describe how to easily export GridView into CSV so that filers and sorting is kept. I do not use any extentions which are so famous today. Note that GridView is not needed, I just want to show the most complicated situation.

Let's say you have page on URL user/index and it contains GridView where you can list and filter users.

Note: In class yii\data\Sort, in method getAttributeOrders(), is the sorting parameter taken from Yii::$app->getRequest() so the name of the sorted column must be in the URL you are using at the moment. This is why sorting might not work if you want to run UserSearch->search() manually without any GET parameters available in Yii::$app->request->queryParams.

The basic method for exporting DataProvider is here:

public function exportDataProviderToCsv($dataProvider) {

  // Setting infinite number of rows per page to receive all pages at once
  $dataProvider->pagination->pageSize = -1;

  // All text-rows will be placed in this array. 
  // We will later use implode() to insert separators and join everything into 1 large string
  $rows = [];

  // UTF-8 header = chr(0xEF) . chr(0xBB) . chr(0xBF)
  // Plus column names in format: 
  // ID;Username;Email etc based on your column names
  $rows [] = chr(0xEF) . chr(0xBB) . chr(0xBF) . User::getCsvHeader();

  foreach ($dataProvider->models as $m) {
    // Method getCsvRow() returns CSV row with values. Example:
    // 1;petergreen;peter.green@gmail.com ...
    $row = trim($m->getCsvRow());
    if ($row!='') {
      $rows[] = $row;  
    }
  }

  // Here we use implode("\n",$rows) to create large string with rows separated by new lines. 
  // Double quotes must be used around \n !
  $csv = implode("\n", $rows);

  $currentDate = date('Y-m-d_H-i-s');

  return \Yii::$app->response->sendContentAsFile($csv, 'users_' . $currentDate . '.csv', [
    'mimeType' => 'application/csv',
    'inline' => false
  ]);
}

If you want to use it to export data from your GridView, modify your action like this:

public function actionIndex($exportToCsv=false) {

  // These 2 rows already existed
  $searchModel = new UserSearch();
  $dataProvider = $searchModel->search(Yii::$app->request->queryParams)
        
  if ($exportToCsv) {
    $this->exportDataProviderToCsv($dataProvider);  
    return;       
  }
  // ...
}

And right above your GridView place this link:

<?php
  $csvUrl = \yii\helpers\Url::current(['exportToCsv'=>1]);
  echo Html::a('Export', $csvUrl, ['class' => 'btn btn-info', 'target'=>'_blank']);
?>

In my code above there were used 2 methods in the model which export things to the CSV format. My implementatino is here:

public static function getCsvHeader() {
  $result = [];
  $result[] = "ID";
  $result[] = "Username";
  $result[] = "Email";
  // ...
  return implode(";", $result);
}
public function getCsvRow() {
  $result = [];
  $result[] = $this->id;
  $result[] = $this->username;
  $result[] = $this->email;
  // ...
  return implode(";", $result);
}

Next chapters had to be moved to a new article!

This article had to be split as I reached the max length. Second part is here:

]]>
0
[wiki] Change default date format in Oracle Fri, 20 Sep 2019 06:15:43 +0000 https://www.yiiframework.com/wiki/2551/change-default-date-format-in-oracle https://www.yiiframework.com/wiki/2551/change-default-date-format-in-oracle lenovo7 lenovo7

Default date format in Oracle is DD-MON-RR (25-JAN-18). With that output, we can't using date formatting.

Too solve this issue, we must change date format oracle like date commonly using

ALTER SESSION SET NLS_DATE_FORMAT = ...

Add this script inside your database connection file

<?php

return [
    'class' => 'yii\db\Connection',
    'dsn' => 'oci:host=127.0.0.1:1521/XE',
    'username' => 'your_username',
    'password' => 'your_password',
    'charset' => 'utf8',

    // Schema cache options (for production environment)
    //'enableSchemaCache' => true,
    //'schemaCacheDuration' => 60,
    //'schemaCache' => 'cache',

    'on afterOpen' => function($event) {
        // $event->sender refers to the DB connection
        $event->sender->createCommand("ALTER SESSION SET NLS_DATE_FORMAT='DD-MM-YYYY hh24:mi:ss'")->execute();
    }    
];
]]>
0
[wiki] Move sources to src Tue, 27 Aug 2019 21:43:50 +0000 https://www.yiiframework.com/wiki/2550/move-sources-to-src https://www.yiiframework.com/wiki/2550/move-sources-to-src samdark samdark

Yii 3 and many Yii 2 package sources are contained within src directory which is convenient since you have less directories to check.

/config
/runtime
/src
  /assets
  /commands
  /controllers
  /mail
  /models
  /views
  /widgets
/tests
/vendor
/web
yii

Let's start with the basic applicaiton template.

  1. Create src directory.
  2. Move source directories there.
  3. Adjust config/web.php:
$config = [
    // ...
    'basePath' => dirname(__DIR__) . '/src',
    'runtimePath' => dirname(__DIR__) . '/runtime',
    'vendorPath' => dirname(__DIR__) . '/vendor',    
    // ...
];

And config/console.php:

$config = [
    // ...
    'basePath' => dirname(__DIR__) . '/src',
    'runtimePath' => dirname(__DIR__) . '/runtime',
    'vendorPath' => dirname(__DIR__) . '/vendor',
    // ...
];

That's it now you have both console and web application source code in src.

]]>
0
[wiki] Nested Set with Yii2 Mon, 01 Apr 2019 07:50:53 +0000 https://www.yiiframework.com/wiki/2549/nested-set-with-yii2 https://www.yiiframework.com/wiki/2549/nested-set-with-yii2 sangprabo sangprabo

The nested set behaviour is an approach to store hierarchical data in relational databases. For example, if we have many categories for our product or items. One category can be a "parent" for other categories, means that one category consists of more than one category. The model can be drawn using a "tree" model. There are other approaches available but what we will learn in this article is specifically the NestedSetsBehavior made by Alexander Kochetov, which utilizing the Modified Preorder Tree Traversal algorithm.

Requirements :

  • Yii2 framework advanced template
  • Yii2 nested sets package

Install the package using composer

It is always recommended to use Composer to install any kind of package or extension for our Yii2-powered project.

$ composer require creocoder/yii2-nested-sets

Create the table

In this article, we will use Category for our model/table name. So we would like to generate the table using our beloved migration tool.

$ ./yii migrate/create create_category_table

We need to modify the table so it contains our desired fields. We also generate three additional fields named position, created_at, and updated_at.

<?php

use yii\db\Migration;

/**
 * Handles the creation for table `category`.
 */
class m160611_114633_create_category extends Migration
{
    /**
     * @inheritdoc
     */
    public function up()
    {
        $this->createTable('category', [
            'id'         => $this->primaryKey(),
            'name'       => $this->string()->notNull(),
            'tree'       => $this->integer()->notNull(),
            'lft'        => $this->integer()->notNull(),
            'rgt'        => $this->integer()->notNull(),
            'depth'      => $this->integer()->notNull(),
            'position'   => $this->integer()->notNull()->defaultValue(0),
            'created_at' => $this->integer()->notNull(),
            'updated_at' => $this->integer()->notNull(),
        ]);
    }

    /**
     * @inheritdoc
     */
    public function down()
    {
        $this->dropTable('category');
    }
}

Then, generate the table using the migration tool.

$ ./yii migrate

If everything is okay, then you could see that a new table named category already exists.

Generate the default CRUD using Gii

To initiate a model, we need to use Gii tool from Yii2. Call the tool from your localhost:8080/gii/model, and fill in the Table Name field with our existing table: category. Fill other fields with appropriate values, and don't forget to give a check to "Generate ActiveQuery" checklist item. This will generate another file that needs to be modified later.

Continue to generate the CRUD for our model with CRUD Generator Tool. Fill in each field with our existing model. After all files are generated, you can see that we already have models, controllers, and views but our work is far from done because we need to modify each file.

Modify models, controllers, and views

The first file we should modify is the model file: Category.

<?php

namespace common\models;

use Yii;
use creocoder\nestedsets\NestedSetsBehavior;

/**
 * This is the model class for table "category".
 *
 * @property integer $id
 * @property string $name
 * @property integer $tree
 * @property integer $lft
 * @property integer $rgt
 * @property integer $depth
 * @property integer $position
 * @property integer $created_at
 * @property integer $updated_at
 */
class Category extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'category';
    }

    public function behaviors() {
        return [
            \yii\behaviors\TimeStampBehavior::className(),
            'tree' => [
                'class' => NestedSetsBehavior::className(),
                'treeAttribute' => 'tree',
                // 'leftAttribute' => 'lft',
                // 'rightAttribute' => 'rgt',
                // 'depthAttribute' => 'depth',
            ],
        ];
    }

    public function transactions()
    {
        return [
            self::SCENARIO_DEFAULT => self::OP_ALL,
        ];
    }

    public static function find()
    {
        return new CategoryQuery(get_called_class());
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['name'], 'required'],
            [['position'], 'default', 'value' => 0],
            [['tree', 'lft', 'rgt', 'depth', 'position', 'created_at', 'updated_at'], 'integer'],
            [['name'], 'string', 'max' => 255],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id'         => Yii::t('app', 'ID'),
            'name'       => Yii::t('app', 'Name'),
            'tree'       => Yii::t('app', 'Tree'),
            'lft'        => Yii::t('app', 'Lft'),
            'rgt'        => Yii::t('app', 'Rgt'),
            'depth'      => Yii::t('app', 'Depth'),
            'position'   => Yii::t('app', 'Position'),
            'created_at' => Yii::t('app', 'Created At'),
            'updated_at' => Yii::t('app', 'Updated At'),
        ];
    }

    /**
     * Get parent's ID
     * @return \yii\db\ActiveQuery 
     */
    public function getParentId()
    {
        $parent = $this->parent;
        return $parent ? $parent->id : null;
    }

    /**
     * Get parent's node
     * @return \yii\db\ActiveQuery 
     */
    public function getParent()
    {
        return $this->parents(1)->one();
    }

    /**
     * Get a full tree as a list, except the node and its children
     * @param  integer $node_id node's ID
     * @return array array of node
     */
    public static function getTree($node_id = 0)
    {
        // don't include children and the node
        $children = [];

        if ( ! empty($node_id))
            $children = array_merge(
                self::findOne($node_id)->children()->column(),
                [$node_id]
                );

        $rows = self::find()->
            select('id, name, depth')->
            where(['NOT IN', 'id', $children])->
            orderBy('tree, lft, position')->
            all();

        $return = [];
        foreach ($rows as $row)
            $return[$row->id] = str_repeat('-', $row->depth) . ' ' . $row->name;

        return $return;
    }
}

As you can see, we import the extension using the keyword use:

use creocoder\nestedsets\NestedSetsBehavior;

and I also add the TimeStampBehavior for our additional fields, created_at and updated_at

\yii\behaviors\TimeStampBehavior::className(),

Next : Our modification to CategoryController file is at update, create, and delete function.

<?php

namespace backend\controllers;

use Yii;
use common\models\Category;
use common\models\CategorySearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * CategoryController implements the CRUD actions for Category model.
 */
class CategoryController extends Controller
{
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
        ];
    }

    /**
     * Lists all Category models.
     * @return mixed
     */
    public function actionIndex()
    {
        $searchModel = new CategorySearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }

    /**
     * Displays a single Category model.
     * @param integer $id
     * @return mixed
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

    /**
     * Creates a new Category model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Category();

        if ( ! empty(Yii::$app->request->post('Category'))) 
        {
            $post            = Yii::$app->request->post('Category');
            $model->name     = $post['name'];
            $model->position = $post['position'];
            $parent_id       = $post['parentId'];

            if (empty($parent_id))
                $model->makeRoot();
            else
            {
                $parent = Category::findOne($parent_id);
                $model->appendTo($parent);
            }

            return $this->redirect(['view', 'id' => $model->id]);
        }

        return $this->render('create', [
                'model' => $model,
            ]);
    }

    /**
     * Updates an existing Category model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ( ! empty(Yii::$app->request->post('Category'))) 
        {
            $post            = Yii::$app->request->post('Category');

            $model->name     = $post['name'];
            $model->position = $post['position'];
            $parent_id       = $post['parentId'];

            if ($model->save())            
            {
                if (empty($parent_id))
                {
                    if ( ! $model->isRoot())
                        $model->makeRoot();
                }
                else // move node to other root 
                {
                    if ($model->id != $parent_id)
                    {
                        $parent = Category::findOne($parent_id);
                        $model->appendTo($parent);
                    }
                }

                return $this->redirect(['view', 'id' => $model->id]);
            }
        }

        return $this->render('update', [
            'model' => $model,
        ]);
    }

    /**
     * Deletes an existing Category model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
        $model = $this->findModel($id);

        if ($model->isRoot())
            $model->deleteWithChildren();
        else 
            $model->delete();

        return $this->redirect(['index']);
    }

    /**
     * Finds the Category model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Category the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Category::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}

As for our views files, we need to remove unnecessary fields from our form, such as the lft, rgt, etc, and add the sophisticated parent field to be a dropdown list. This requires a lot of effort, as you can see on the getTree function on our model.

<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;

use common\models\Category;

/* @var $this yii\web\View */
/* @var $model common\models\Category */
/* @var $form yii\widgets\ActiveForm */
?>

<div class="category-form">

    <?php $form = ActiveForm::begin(); ?>

    <?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>

    <div class='form-group field-attribute-parentId'>
    <?= Html::label('Parent', 'parent', ['class' => 'control-label']);?>
    <?= Html::dropdownList(
        'Category[parentId]',
        $model->parentId,
        Category::getTree($model->id),
        ['prompt' => 'No Parent (saved as root)', 'class' => 'form-control']
    );?>

    </div>

    <?= $form->field($model, 'position')->textInput(['type' => 'number']) ?>

    <div class="form-group">
        <?= Html::submitButton($model->isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
    </div>

    <?php ActiveForm::end(); ?>

</div>

Complete files can be found at my GitHub page: https://github.com/prabowomurti/learn-nested-set

The screencast (subtitled English) here : https://www.youtube.com/watch?v=MjJEjF1arHs

]]>
0
[wiki] Events registration examples Mon, 11 Mar 2019 14:04:14 +0000 https://www.yiiframework.com/wiki/2548/events-registration-examples https://www.yiiframework.com/wiki/2548/events-registration-examples minitia82 minitia82

Register an event handler at Object-Level

e.g inside the init method of a Model

// this should be inside your model class. For example User.php
public function init(){
  $this->on(self::EVENT_NEW_USER, [$this, 'sendMail']);
  $this->on(self::EVENT_NEW_USER, [$this, 'notification']);
  // first parameter is the name of the event and second is the handler. 
  // For handlers I use methods sendMail and notification
  // from $this class.
  parent::init(); // DON'T Forget to call the parent method.
}

from https://stackoverflow.com/questions/28575636/how-to-use-events-in-yii2

Register an event handler at Class-Level

To register event handlers at Class-Level a good place can be inside the bootstrap process.

So, you can put the registration code as

Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
    Yii::debug(get_class($event->sender) . ' is inserted');
});

in a php file like the bootstrap.php used in Advanced-Template or through configuration, with your custom component for example

see docs

(https://www.yiiframework.com/doc/guide/2.0/en/structure-applications#bootstrap or https://www.yiiframework.com/doc/guide/2.0/en/structure-extensions#bootstrapping-classes)

]]>
0
[wiki] (draft) Understanding Yii 3 Sun, 16 Feb 2020 13:25:06 +0000 https://www.yiiframework.com/wiki/2547/draft-understanding-yii-3 https://www.yiiframework.com/wiki/2547/draft-understanding-yii-3 machour machour

Understanding Yii 3

  1. Introduction
  2. Changes overview
  3. Yii 3 composer packages
  4. Running your first Yii 3 powered application

Note: The information here is outdated. You can check yii-demo for current Yii 3 features.

Since this Wiki page is getting bigger and bigger, I decided to document things in a Yii 3 project instead.

Project source can be found at https://github.com/machour/yii3-kitchen-sink Live web site: https://yii3.idk.tn/

Follow the repo/website to get fresher informations, or better yet, pull the project and run it by yourself to get acquainted with Yii3

Introduction

This document is intended for an audience already familiar with Yii2. It's meant to bring together all information related to Yii 3 in one place to make it easier to get on track.

Yii 3 is the second major rewrite of the Yii framework.

Originally started in the 2.1 branch, it was later decided to switch to the 3.X series because of all the backward compatibility breakage. Starting with 3.0, Yii will follow the Sementic Versionning.

This rewrite addresses a lot of issues Yii 2 suffered from, like the framework being too coupled with jQuery, bower, bootstrap. [TODO: add more grieffs about Yii2]

Changes overview

Here are the main changes in Yii 3. You can check the complete CHANGELOG for an exhaustive list.

Source code splitting

The framework source code have been split into several packages, and at its core level, Yii no longer makes assumptions about your development stack, or the features you will be using.

This enable you to cherry pick the packages you need to compose your application.

This re-organisation is also a great news for maintainance, as these packages will be released separately, thus allowing more frequent updates.

Autoloading

The custom PHP class autoloader have been removed in favor of Composer's PSR-4 implementation. This means that in order for Yii to see your classes, you will have to explicitly register your namespace in composer.json. We will see an example later.

PSR compatibility

Yii 3 takes some positive steps following the PHP-FIG recommendations, by implementing the following PSRs:

  • Logging is now compliant with PSR-3
  • Caching is now compliant with PSR-16
  • Dependency Injection is now compliant with PSR-11
Application configuration

If you've ever installed an extension using Yii 2, you may/certainly have found yourself on the extension README file, looking for the chunk of configuration to copy/paste in your own config/main.php file.

This can often lead to:

  • a huge configuration file (which you may have decided to split into smaller files)
  • non-trivials configurations update when a new version of the extension is realeased with new/changed configurations options.

Yii 3 takes another approach. Every package bundle its own configuration, and will probably work out of the box. And you may override them, if you need to, from your configuration file.

This is all done by leveraging the hiqdev/composer-config-plugin composer plugin, which takes care of scanning & merging all the configurations when you run composer dump-autoload (also know as composer du).

You can read Yii2 projects alternative organization for an in-depth explanation of the motivation behind hiqdev/composer-config-plugin.

Packages authors will have the responsibility to avoid introducing BC breaks, by adopting a strict sementical versionning.

Dependencies injection

[TODO]

Yii 3 composer packages

Here are the new packages introduced in Yii 3, which can be found in this official list.

Let's introduce them briefly:

The Framework

This is the new kernel of Yii. It defines the base framework and its core features like behaviors, i18n, mail, validation..

You will rarely want to directly install yiisoft/yii-core. Instead, you will install one or more of the following:

This three packages, considered as Extensions, are responsible for implementing the basic functionnalities of each "channel" they refer to:

  • yii-console implements all that you need to build a console application (the base Controller for commands, the Command helper, ..)
  • yii-web implements all that you need to build a web application (Assets management, Sessions, Request handling ..)
  • yii-rest implements all that you need to build a REST interface (ActiveController, ..)
Librairies

In Yii 3, libraries do not depend on Yii and are meant to be usable outside the framework. Their package name is yiisoft/something without yii-prefix.

Drivers for yiisoft/db

The various drivers for DB have also been separated into packages:

Extensions

Extensions depends (at least) on yii-core. Aside from the 3 extensions already encountered above (yii-console, yii-web, yii-api), these packages are available

Development
View rendering engines
Data rendering
JS & CSS Frameworks integration
Widgets
Misc
Yii project template and application bases

This is a very basic Yii project template, that you can use to start your development.

You will probably want to pick one or more of these three starters to install in your project next:

Let's try running the web base template in the next section.

Running your first Yii 3 powered application

Let's try running a web application using Yii 3, and the provided project template.

Installing the project template
composer create-project --prefer-dist --stability=dev yiisoft/yii-project-template myapp
cd myapp

Here's the created structure:

.
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── config
│   ├── common.php
│   └── params.php
├── docker-compose.yml
├── hidev.yml
├── public
│   ├── assets
│   ├── favicon.ico
│   ├── index.php
│   └── robots.txt
├── runtime
└── vendor

You won't be able to start the web server right away using ./vendor/bin/yii serve, as it will complain about not knowing the "app" class.

In fact, this project template only introduce the bare minimum in your application: Caching, Dependencies injection, and Logging. The template doesn't make an assumption about the kind of application you're building (web, cli, api).

You could start from scratch using this bare template, select the extensions & packages you want to use and start developing, or you can pick one of the three starters provided.

Installing the web starter

Since we're doing a web application, we will need an asset manager. We can pick either one of those:

  • Asset-packagist & composer-merge-plugin (requires only PHP)
  • Foxy (requires npm or yarn)

Let's go with foxy (personal taste since composer is so slow from Tunisia):

composer require "foxy/foxy:^1.0.0"

We can now install the yii-base-web starter and run our application:

composer require yiisoft/yii-base-web
vendor/bin/yii serve

By visiting http://localhost:8080/, you should now see something like this:

50153967-44a6af00-02c8-11e9-9914-ceb463065cdf.png

Checking back our project structure, nothing really changed, aside from the creation of these three entries:

  • node_modules/
  • package-lock.json
  • package.json

So where do what we see in the browser comes from ?

Exploring yiisoft/yii-base-web structure:

If you explore the folder in vendor/yiisoft/yii-base-web, you will see that the template is in fact a project itself, with this structure:

.
├── LICENSE.md
├── README.md
├── composer.json
├── config
│   ├── common.php
│   ├── console.php
│   ├── env.php
│   ├── messages.php
│   ├── params.php
│   └── web.php
├── phpunit.xml.dist
├── public
│   └── css
│       └── site.css
├── requirements.php
├── runtime
└── src
    ├── assets
    │   └── AppAsset.php
    ├── commands
    │   └── HelloController.php
    ├── controllers
    │   └── SiteController.php
    ├── forms
    │   ├── ContactForm.php
    │   └── LoginForm.php
    ├── mail
    │   └── layouts
    ├── messages
    │   ├── et
    │   ├── pt-BR
    │   ├── ru
    │   └── uk
    ├── models
    │   └── User.php
    ├── views
    │   ├── layouts
    │   └── site
    └── widgets
        └── Alert.php

The folders and files should make sense to you if you already developed applications using Yii2 and the basic template.

]]>
0
[wiki] Batch Gridview data ajax send splitted in chunks displaying bootstrap Progress bar Sat, 24 Nov 2018 10:48:47 +0000 https://www.yiiframework.com/wiki/2546/batch-gridview-data-ajax-send-splitted-in-chunks-displaying-bootstrap-progress-bar https://www.yiiframework.com/wiki/2546/batch-gridview-data-ajax-send-splitted-in-chunks-displaying-bootstrap-progress-bar toaster toaster

The scenario in which this wiki can be useful is when you have to send an (huge) array of model ids and perform a time consuming computation with it like linking every model to other models. The idea is to split the array into smaller arrays and perform sequential ajax requests, showing the calculation progress using a Bootstrap Progress bar.

I have created a Country model and generated the CRUD operations using Gii. The model class look like this:

namespace app\models;
use Yii;
use yii\helpers\ArrayHelper;

/**
 * This is the model class for table "country".
 *
 * @property string $code
 * @property string $name
 * @property int $population
 *
 */
class Country extends \yii\db\ActiveRecord {

    /**
     * {@inheritdoc}
     */
    public static function tableName() {
        return 'country';
    }

    /**
     * {@inheritdoc}
     */
    public function rules() {
        return [
            [['code', 'name'], 'required'],
            [['population'], 'integer'],
            [['code'], 'string', 'max' => 3],
            [['name'], 'string', 'max' => 52],
            [['code'], 'unique'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels() {
        return [
            'code' => 'Code',
            'name' => 'Name',
            'population' => 'Population',
        ];
    }
    /**
     * 
     * @return array
     */
    public static function getCodes(){
        return array_keys(ArrayHelper::map(Country::find()->select('code')->asArray()->all(), 'code', 'code'));
    }
}

I have also created another model that will take care to validate the ids and perform the time consuming calculation through the process() function. (in this example I just use a sleep(1) that will wait for one second for each element of the $codes array). I also want to keep track of the processed models, incrementing the $processed class attribute.

<?php

namespace app\models;

use app\models\Country;

/**
 * Description of BatchProcessForm
 *
 * @author toaster
 */
class BatchProcessForm extends \yii\base\Model {

    /**
     * The codes to process
     * @var string
     */
    public $codes;

    /**
     * The number of codes processed
     * @var integer
     */
    public $processed = 0;

    /**
     * @return array the validation rules.
     */
    public function rules() {
        return [
            ['codes', 'required'],
            ['codes', 'validateCodes'],
        ];
    }

    /**
     * Check whether codes exists in the database
     * @param type $attribute
     * @param type $params
     * @param type $validator
     */
    public function validateCodes($attribute, $params, $validator) {
        $input_codes = json_decode($this->$attribute);
        if (null == $input_codes || !is_array($input_codes)) {
            $this->addError($attribute, 'Wrong data format');
            return;
        }
        $codes = Country::getCodes();
        if (!array_intersect($input_codes, $codes) == $input_codes) {
            $this->addError($attribute, 'Some codes selected are not recognized');
        }
    }

    /**
     * Process the codes
     * @return boolean true if everything goes well
     */
    public function process() {
        if ($this->validate()) {
            $input_codes = json_decode($this->codes);
            foreach ($input_codes as $code) {
                sleep(1);
                $this->processed++;
            }
            return true;
        }
        return false;
    }

}

The code for the controller action is the following:

/**
 * Process an array of codes
 * @return mixed
 * @throws BadRequestHttpException if the request is not ajax
 */
public function actionProcess(){
    if(Yii::$app->request->isAjax){
        Yii::$app->response->format = Response::FORMAT_JSON;
        $model = new BatchProcessForm();
        if($model->load(Yii::$app->request->post()) && $model->process()){
            return ['result' => true, 'processed' => $model->processed];
        }
        return ['result' => false, 'error' => $model->getFirstError('codes')];
    }
    throw new \yii\web\BadRequestHttpException;
}

In my index.php view I have added a CheckboxColumn as first column of the Gridview that allows, out of the box, to collect the ids of the models selected via Javascript (in this case the values of the code attribute). I have also added a button-like hyperlink tag to submit the collected data (with id batch_process) and a Bootstrap Progress bar inside a Modal Window. The code of my view is the following:

<?php

use yii\helpers\Html;
use yii\grid\GridView;
use yii\bootstrap\Modal;

/* @var $this yii\web\View */
/* @var $searchModel app\models\CountrySearch */
/* @var $dataProvider yii\data\ActiveDataProvider */

$this->title = 'Countries';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="country-index">

    <h1><?= Html::encode($this->title) ?></h1>
    <?php // echo $this->render('_search', ['model' => $searchModel]); ?>

    <p>
        <?= Html::a('Create Country', ['create'], ['class' => 'btn btn-success']) ?>
        <?= Html::a('Batch Process', ['process'], ['class' => 'btn btn-info', 'id' => 'batch_process']) ?>
    </p>

    <?=
    GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'options' => [
            'id' => 'country-index'
        ],
        'columns' => [
            ['class' => 'yii\grid\CheckboxColumn'],
            'code',
            'name',
            'population',
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]);
    ?>
</div>

<?php Modal::begin(['header' => '<h5 id="progress">0% Complete</h5>', 'id' => 'progress-modal', 'closeButton' => false]); ?>

<?=
yii\bootstrap\Progress::widget([
    'percent' => 0,
     'options' => ['class' => 'progress-success active progress-striped']
]);
?>

<?php Modal::end(); ?>

<?php $this->registerJsFile('@web/js/main.js', ['depends' => [\app\assets\AppAsset::class]]); ?>

The Javascript file that split in chunks the array and send each chunk sequentially is main.js. Although I have registered the Javascript file using registerJsFile() method I highly recommend to better handle and organize js files using Asset Bundles. Here is the code:


function updateProgressBar(percentage) {
    var perc_string = percentage + '% Complete';
    $('.progress-bar').attr('aria-valuenow', percentage);
    $('.progress-bar').css('width', percentage + '%');
    $('#pb-small').text(perc_string);
    $('#progress').text(perc_string);
}

function disposeProgressBar(message) {
    alert(message);
    $('#progress-modal').modal('hide');
}

function showProgressBar() {
    var perc = 0;
    var perc_string = perc + '% Complete';
    $('.progress-bar').attr('aria-valuenow', perc);
    $('.progress-bar').css('width', perc + '%');
    $('#pb-small').text(perc_string);
    $('#progress').text(perc_string);
    $('#progress-modal').modal('show');
}

function batchSend(set, iter, take, processed) {
    var group = iter + take < set.length ? iter + take : set.length;
    var progress = Math.round((group / set.length) * 100);
    var dataObj = [];
    for (var i = iter; i < group; i++) {
        dataObj.push(set[i]);
    }
    iter += take;
    $.ajax({
        url: '/country/process', ///?r=country/process
        type: 'post',
        data: {'BatchProcessForm[codes]': JSON.stringify(dataObj)},
        success: function (data) {
            if (data.result) {
                updateProgressBar(progress);
                processed += data.processed;
                if (progress < 100) {
                    batchSend(set, iter, take, processed);
                } else {
                    var plural = processed == 1 ? 'country' : 'countries';
                    disposeProgressBar(processed + ' ' + plural + ' correctly processed');
                }
                return true;
            }
            disposeProgressBar(data.error);
            return false;
        },
        error: function () {
            disposeProgressBar('Server error, please try again later');
            return false;
        }
    });
}

$('#batch_process').on('click', function (e) {
    e.preventDefault();
    var keys = $('#country-index').yiiGridView('getSelectedRows');
    if (keys.length <= 0) {
        alert('Select at least one country');
        return false;
    }
    var plural = keys.length == 1 ? 'country' : 'countries';
    if (confirm(keys.length + ' ' + plural + ' selected.. continue?')) {
        showProgressBar();
        batchSend(keys, 0, 5, 0);
    }
});

The first three functions take care to show, update and hide the progress bar inside the modal window, while the batchSend function perform the array split and, using recursion, send the data through post ajax request, encoding the array as JSON string and calling itself until there is no more chunks to send. The last lines of code bind the click event of the #batch_process button checking if at least one row of Gridview has been selected. The number of elements on each array chunk can be set as the third argument of the batchSend function (in my case 5), you can adjust it accordingly. The first parameter is the whole array obtained by the yiiGridView('getSelectedRows') function. The final result is something like this:

batch_send1.gif

...looks cool, isn't it?

]]>
0
[wiki] Using multiple models in an identity Tue, 29 Jan 2019 23:11:01 +0000 https://www.yiiframework.com/wiki/2545/using-multiple-models-in-an-identity https://www.yiiframework.com/wiki/2545/using-multiple-models-in-an-identity samdark samdark

Let's assume we have two models: Customer and Supplier and we want both to log in. Yii is quite flexible when it comes to authentication and authorization so it's possible.

First of all, Yii assumes that there's a single type of user component used at once. Still, we're able to create a universal Identity that works with both customers and suppliers.

So we create models/Identity.php:

final class Identity implements IdentityInterface
{
    const TYPE_CUSTOMER = 'customer';
    const TYPE_SUPPLIER = 'supplier';

    const ALLOWED_TYPES = [self::TYPE_CUSTOMER, self::TYPE_SUPPLIER];

    private $_id;
    private $_authkey;
    private $_passwordHash;

    public static function findIdentity($id)
    {
        $parts = explode('-', $id);
        if (\count($parts) !== 2) {
            throw new InvalidCallException('id should be in form of Type-number');
        }
        [$type, $number] = $parts;

        if (!\in_array($type, self::ALLOWED_TYPES, true)) {
            throw new InvalidCallException('Unsupported identity type');
        }

        $model = null;
        switch ($type) {
            case self::TYPE_CUSTOMER:
                $model = Customer::find()->where(['id' => $number])->one();
                break;
            case self::TYPE_SUPPLIER:
                $model = Supplier::find()->where(['id' => $number])->one();
                break;
        }

        if ($model === null) {
            return false;
        }


        $identity = new Identity();
        $identity->_id = $id;
        $identity->_authkey = $model->authkey;
        $identity->_passwordHash = $model->password_hash;
        return $identity;
    }

    public static function findIdentityByAccessToken($token, $type = null)
    {
        $model = Customer::find()->where(['token' => $token])->one();
        if (!$model) {
            $model = Supplier::find()->where(['token' => $token])->one();
        }

        if (!$model) {
            return false;
        }

        if ($model instanceof Customer) {
            $type = self::TYPE_CUSTOMER;
        } else {
            $type = self::TYPE_SUPPLIER;
        }

        $identity = new Identity();
        $identity->_id = $type . '-' . $model->id;
        $identity->_authkey = $model->authkey;
        $identity->_passwordHash = $model->password_hash;
        return $identity;
    }

    public function validatePassword($password)
    {
        return password_verify($password, $this->_passwordHash);
    }

    public static function findIdentityByEmail($email)
    {
        $model = Customer::find()->where(['email' => $email])->one();
        if (!$model) {
            $model = Supplier::find()->where(['email' => $email])->one();
        }

        if (!$model) {
            return false;
        }

        if ($model instanceof Customer) {
            $type = self::TYPE_CUSTOMER;
        } else {
            $type = self::TYPE_SUPPLIER;
        }

        $identity = new Identity();
        $identity->_id = $type . '-' . $model->id;
        $identity->_authkey = $model->authkey;
        $identity->_passwordHash = $model->password_hash;
        return $identity;
    }

    public function getId()
    {
        return $this->_id;
    }

    public function getAuthKey()
    {
        return $this->_authkey;
    }

    public function validateAuthKey($authKey)
    {
        return $this->getAuthKey() === $authKey;
    }
}

In the above we assume that our ids are like customer-23 or supplier-34. When we need to get identity instance we split the id by - and getting both type and integer id for the model corresponding to that type.

Having identity we can tell Yii to use it via config/main.php:

[
    // ...
    'components' => [
        // ...
        'user' => [
            'identityClass' => 'app\models\Identity',
            'enableAutoLogin' => true,
        ],
    ],
],

The only thing left is to adjust models\LoginForm.php:

class LoginForm extends Model
{
    // ...
    
    public function getUser()
    {
        if ($this->_user === false) {
            $this->_user = Identity::findIdentityByEmail($this->username);
        }

        return $this->_user;
    }
}

That's it. Now you can log in using both models.

]]>
0
[wiki] Update and Delete buttons on Breadcrumb Tue, 29 Sep 2020 23:23:56 +0000 https://www.yiiframework.com/wiki/2544/update-and-delete-buttons-on-breadcrumb https://www.yiiframework.com/wiki/2544/update-and-delete-buttons-on-breadcrumb adinugro adinugro

The definition of breadcrumbs according to its documentation is as follow: Breadcrumbs displays a list of links indicating the position of the current page in the whole site hierarchy.

We can define the breadcrumbs easily by adding these lines.

$this->title = $model->formNo;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Supplier receiving'), 'url' => ['index', 'type' => $model->type]];
$this->params['breadcrumbs'][] = ['label' => $this->title, 'url' => ['view', 'id' => $model->id]];

Reading the documentation, I encountered the template keyword. I was excited about the possibility to add buttons into breadcrumbs. Add these lines and you would have buttons on the right side of the breadcrumb.

$this->title = $model->formNo;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Supplier receiving'), 'url' => ['index', 'type' => $model->type]];
$this->params['breadcrumbs'][] = ['label' => $this->title, 'url' => ['view', 'id' => $model->id]];
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Delete'), 'template' =>
    Html::tag('span', Html::a(Html::icon('glyphicon glyphicon-trash white') . ' ' . Yii::t('app', 'Delete'), Url::to(['delete', 'id' => $model->id]), [
                'class' => 'btn btn-xs btn-danger',
                'title' => Yii::t('app', 'Delete'),
                'data-pjax' => '0',
                'data-method' => 'POST',
                'data-confirm' => Yii::t('app', 'Are you sure you want to delete this supplier receiving?'),
                    ]
            ), ['class' => 'pull-right'])];
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Update'), 'template' => Html::tag('span', Html::a(
                    Html::icon('glyphicon glyphicon-pencil') . ' ' . Yii::t('app', 'Update'), Url::to(['update', 'id' => $model->id, 'inview' => 1]), [
                'class' => 'btn btn-xs btn-warning',
                'title' => Yii::t('app', 'Update'),
                'role' => 'modal-remote',
                'data-toggle' => 'tooltip',
                    ]
            ), ['class' => 'pull-right', 'style' => 'margin-right: 5px;'])];

Actually you can use this line to define the template, but $links means the full link. A suggestion to the developer is to make the $url and $label variables to be available like $links, so that we can make the template more flexible and meaningful than above codes.

FYI,

  1. I use ajaxcrud extension, so that the update link has 'role' => 'modal-remote' to allow the update process taken place on the modal window. I added a inview parameter to distinguish the update from index or view. Both actions will be called using ajax request and will show the form on the modal, but the index update will redirect to index and refresh gridview while in the view, it should redirect to the view.
  2. On the template, I used span over li to make ' / ' character not appear on the links buttons.

A simple tip to make the view layout efficient.

]]>
0
[wiki] When to use Active Record Sat, 23 May 2020 07:32:04 +0000 https://www.yiiframework.com/wiki/2541/when-to-use-active-record https://www.yiiframework.com/wiki/2541/when-to-use-active-record samdark samdark

When to use Active Record is a common question among developers, Yii and overall.

I have about 500K records in my DB tables and each query there are 2 or 3 joins. I was getting data via AR, about a hundred records a time and noticed that using createCommand consumes less memory and CPU. I think with DAO instead of AR it will be faster.

It is true that Active Record consumes memory for storing objects and CPU cycles for instantiate these objects.

Is AR bad? Is it for small projects only? We have lots of inserts, about 5000 records an hour and we're using AR for that. Should we re-write?

AR is a pleasure to use when you're managing not that many records at the same time. CRUD is very easy with it. Yii 2 dirty attribute support (only what was modified is saved) off-loads database and mitigates many parallel editing conflicts. If you don't have complex logic in your app and you don't need entity abstractions, AR is an ideal choice.

AR is OK for queries not too complicated when they return no more than a hundred objects. Yes, it is faster and less memory consuming to use query builder or asArray() but working with arrays is significantly less convenient.

For complex queries, such as aggregates or reports, AR isn't recommended. These are easier to express with query builder or SQL. Same goes for import and export routines.

]]>
0
[wiki] Getting information from the current locale Thu, 09 Aug 2018 18:56:33 +0000 https://www.yiiframework.com/wiki/2540/getting-information-from-the-current-locale https://www.yiiframework.com/wiki/2540/getting-information-from-the-current-locale CeBe CeBe

Yii 2.0 comes with a formatter component to format dates, numbers, and other values for international users according to their locale. This is very useful for displaying data. When working with incoming data or when using enhanced input methods like the MaskedInput widget you sometimes need to access the symbols used by the formatter to generate a pattern or parse the input.

The Yii formatter does not have methods to access this data, it relies on the PHP intl extension for formatting. If you want to access the formatting symbols you need to work with intl directly.

Getting decimal and thousand separator

$locale = 'de_DE';

$formatter = new \NumberFormatter($locale,\NumberFormatter::DECIMAL);

$decimalSeparator = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
$thousandSeparator = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL);

There are more symbols than shown above, for the full list of symbols see the list of NumberFormatter constants in the php manual.

Getting a currency symbol

Getting the currency symbol is a bit more complicated if you do not want the symbol of the default currency of a locale, but another currency:

$locale = 'de_DE'; // get the currency symbol of Germanys default currency EUR = "€"
$locale = 'de_DE@currency=USD'; // get the currency symbol of USD in German locale = "$"

$formatter = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);

$currencySymbol = $formatter->getSymbol(\NumberFormatter::CURRENCY_SYMBOL);

Note, that the NumberFormatter is instantiated with the CURRENCY constant instead of DECIMAL here.

Since Yii 2.0.14 Yii has this method built-in in the Locale class.

]]>
0
[wiki] What SQL-Statement creates yii? Fri, 27 Jul 2018 11:28:13 +0000 https://www.yiiframework.com/wiki/2539/what-sql-statement-creates-yii https://www.yiiframework.com/wiki/2539/what-sql-statement-creates-yii Necip Necip

The usual way to find out what Yii has created for an SQL query is to mutilate the SQL in the sourcecode and call the program again so that the SQL statement with errors is displayed. Or you can use the SQL logger, which must be switched on and off each time and logs all SQL statements, which leads to an enormous slowdown in program execution and decelerates your workflow.

These all is not necessary if you use XDebug and place a breakpoint after the function "getCommandBuilder" in the CActiveRecord.php and take a closer look at the return value in $command.

This database access is created in yii:

yii-sql11.png?w=474

This code is called by the yii framework

yii-sql21.png?w=474

getCommandBuilder returns this data structure with the generated SQL statement:

yii-sql32.png?w=474

Path to CActiveRecord.php

C:\xampp\htdocs\your_project\yii\framework\db\ar\CActiveRecord.php

]]>
0