Yii 2 RESTful API Setup
  • Download and setup advanced Yii 2 application.
  • Download https://github.com/deerawan/yii2-advanced-api, and extract and copy [app]/api folder to our application (at the same folder level).
    • Advanced App: The ideas is to create API folder at same level as frontend and backend folders. Eg:
      backend
      common
      frontend
      ...
      api
      -- common
      ------ controllers
      ------ models
      -- config
      -- modules
      ------ v1
      ---------- controllers
      ---------- models
      ------ v2
      ---------- controllers
      ---------- models
      -- runtime
      -- tests
      -- web
    • Basic App: The ideas is to create API folder at same level as controllers and models folders. Eg:
      controllers
      models
      views
      ...
      api
      -- config
      -- modules
      ------ v1
      ---------- controllers
      ---------- models
      ------ v2
      ---------- controllers
      ---------- models
      -- runtime
      -- tests
      -- web

Reference: https://github.com/deerawan/yii2-advanced-api

Create Models

Create the table distributor. Eg:

CREATE TABLE `distributor` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`first_name` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`last_name` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`name_prefix` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`occupation` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`company_name` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`address` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`city` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`state_prov` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`postal_code` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`country` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`latitude` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`longitude` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`phone` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`fax` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`email` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`website` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`services` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin',
	`hours` TEXT NULL COLLATE 'utf8_bin',
	`instructions` TEXT NULL COLLATE 'utf8_bin',
	`status` SMALLINT(6) NOT NULL DEFAULT '1',
	`created_by` INT(11) NOT NULL,
	`created_at` DATETIME NOT NULL,
	`updated_by` INT(11) NOT NULL,
	`updated_at` DATETIME NOT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=27
;

For our table distributor, we create the following model in [app]/api/modules/v1/models:

<?php
namespace api\modules\v1\models;
 
use \yii\db\ActiveRecord;
/**
 * Distributor Model
 */
class Distributor extends ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'distributor';
    }
 
    /**
     * @inheritdoc
     */
    public static function primaryKey()
    {
        return ['id'];
    }
 
    /**
     * Define rules for validation
     */
    public function rules()
    {
        return [
            [['id', 'company_name', 'address'], 'required']
        ];
    }
}
Create Controllers

For our model distributor, we create the controller in [app]/api/modules/v1/controllers:

<?php
 
namespace api\modules\v1\controllers;
 
use yii\rest\ActiveController;
 
/**
 * Distributor Controller API
 */
class DistributorController extends ActiveController
{
    public $modelClass = 'api\modules\v1\models\Distributor';    
}
Setup Configuration

We need to add references to our API v1 module in the configuration file in [app]/api/config/main.php:

<?php
 
$params = array_merge(
    //require(__DIR__ . '/../../config/params.php'),            // Basic app
    //require(__DIR__ . '/../../config/params-local.php'),      // Basic app
    require(__DIR__ . '/../../common/config/params.php'),       // Advanced app
    require(__DIR__ . '/../../common/config/params-local.php'), // Advanced app
    require(__DIR__ . '/params.php'),                           // API params
    require(__DIR__ . '/params-local.php')                      // API params
);
 
return [
    'id' => 'app-api',
    'basePath'  => dirname(__DIR__),    // equivalent to [app]\api
    'bootstrap' => ['log'],
    //
    // Define modules API
    //
    'modules' => [
        // API v1
        'v1' => [
            'basePath' => '@app/modules/v1',
            'class'    => 'api\modules\v1\Module'
        ],
        // API v2
        'v2' => [
            'basePath' => '@app/modules/v2',
            'class'    => 'api\modules\v2\Module'
        ]
    ],
    'components' => [        
        'user' => [
            //'identityClass'   => 'common\models\User',
            'identityClass' => 'api\modules\v1\models\User',
            'enableAutoLogin' => false,
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class'  => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
 
        // NOTE: If strict parsing is enabled, the incoming requested URL must match 
        // at least one of the rules in order to be treated as a valid request, 
        // or a yii\web\NotFoundHttpException will be thrown. If strict parsing 
        // is disabled, when none of the rules matches the requested URL, 
        // the path info part of the URL will be treated as the requested route.
        'urlManager' => [
            'enablePrettyUrl'     => true,
            'enableStrictParsing' => true,
            'showScriptName'      => false,
            'rules'   => [
                ['class' => 'yii\rest\UrlRule', 'controller' => 'v1/item-product'],
                [
                    'class'      => 'yii\rest\UrlRule', 
                    'controller' => 'v1/distributor',
                    //'tokens'     => [
                    //    '{id}'   => '<id:\\w+>'
                    //],
                    //'except'     => ['delete', 'create', 'update'],
                ],
                //[
                //    //-----------------------------------------------------------------------------
                //    // LEGEND:  'pattern' => 'route'
                //    // See more: http://www.yiiframework.com/doc-2.0/guide-runtime-routing.html
                //    // RegEx notation in use:
                //    //   \d	    Any digit.
                //    //   \d+	    One or more of a digit.
                //    //   \w	    Any word/alphanumeric character (letter, number, underscore).
                //    //   -	    Literal dash in controller name.
                //    //   [\w-]+	One or more alphanumeric or dash characters.
                //    //-----------------------------------------------------------------------------
                //    'dashboard' => 'site/index',
                //
                //     // RESTful rules (Examples)
                //    'entry/<id:\d+>'       => 'entry/view', // same as 'GET entry/<id:\d+>' => 'entry/view',
                //    'PUT entry/<id:\d+>'   => 'entry/update'
                //    'POST,PUT entry/index' => 'entry/create'
                //    'POST <controller:[\w-]+>s'           => '<controller>/create',
                //    '<controller:[\w-]+>s'                => '<controller>/index',
                //    'PUT <controller:[\w-]+>/<id:\d+>'    => '<controller>/update',
                //    'DELETE <controller:[\w-]+>/<id:\d+>' => '<controller>/delete',
                //    '<controller:[\w-]+>/<id:\d+>'        => '<controller>/view',
                //    
                //    '<controller:(entry|comment)>/create'                            => '<controller>/create',
                //    '<controller:(entry|comment)>/<id:\d+>/<action:(update|delete)>' => '<controller>/<action>',
                //    '<controller:(entry|comment)>/<id:\d+>'                          => '<controller>/view',
                //    '<controller:(entry|comment)>s'                                  => '<controller>/index',
                //],
                //
                //['class' => 'yii\rest\UrlRule', 'controller' => 'user'], is equivalent to:
                //[
                //    'PUT,PATCH users/<id>' => 'user/update',
                //    'DELETE users/<id>'    => 'user/delete',
                //    'GET,HEAD users/<id>'  => 'user/view',
                //    'POST users'           => 'user/create',
                //    'GET,HEAD users'       => 'user/index',
                //    'users/<id>'           => 'user/options',
                //    'users'                => 'user/options',
                //],
            ],        
        ],
        'formatter' => [
           'class'            => 'yii\i18n\Formatter',
           'dateFormat'       => 'php:M d, Y',
           //'datetimeFormat' => 'php:M d, Y H:i:s',
           'timeFormat'       => 'php:H:i:s', 
           'defaultTimeZone'  => 'America/New_York'
        ],
        'request' => [
            'parsers' => [
                'application/json' => 'yii\web\JsonParser',
            ]
        ]
    ],
    'params' => $params,
];

Include local configuration settings for the database in [app]/api/config/main-local.php:

<?php
return [
    'components' => [
        'db' => [
            'class'    => 'yii\db\Connection',
            'dsn'      => 'mysql:host=localhost;dbname=acme_website',
            'username' => 'acmeusr',
            'password' => 'acmepass',
            'charset'  => 'utf8',
        ],
        'mailer' => [
            'class'    => 'yii\swiftmailer\Mailer',
            'viewPath' => '@common/mail',
        ],
    ],
];

Add any global params in configuration file [app]/api/config/params.php:

<?php
return [
    'adminEmail' => 'admin@example.com',
];

Add any local params in configuration file [app]/api/config/params-local.php:

<?php
return [
];
Add API Alias

Create file and add an alias in [app]/common/config/aliases.php (Advanced app) or [app]/config/aliases.php (Basic app):

<?php
// for system-wide aliases
require(__DIR__ . '/bootstrap.php');  
 
// add API alias
//Yii::setAlias('@api', __DIR__ . '/api');                   // for Basic app 
Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api');   // for Advanced app
?>

Or simply add the following line to the file [app]/common/config/bootstrap.php (Advanced app) or [app]/config/web.php (Basic app):

...
// Method 1:
//Yii::setAlias('@api', dirname(__DIR__) . '/api');            // for Basic app
//Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api');   // for Advanced app
 
// Method 2:
$config = [
    //...
    'aliases' => [
        //...
        '@api'=> dirname(__DIR__) . '/api',
    ],
];    
Setup Web Folder

Create file [app]/api/web/.htaccess (very important!!):

# use mod_rewrite for pretty URL support
RewriteEngine on
 
# If a directory or a file exists, use the request directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
 
# Otherwise forward the request to index.php
RewriteRule . index.php

Create [app]/api/web/index.php. For Advanced app:

<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
 
require(__DIR__ . '/../../vendor/autoload.php');
require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../common/config/aliases.php');
 
$config = yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../../common/config/main.php'),
    require(__DIR__ . '/../../common/config/main-local.php'),
    require(__DIR__ . '/../config/main.php'),
    require(__DIR__ . '/../config/main-local.php')
);
 
$application = new yii\web\Application($config);
$application->run();

For Basic app:

<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
 
require(__DIR__ . '/../../vendor/autoload.php');
require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
 
$config = yii\helpers\ArrayHelper::merge(
    require(__DIR__ . '/../../config/web.php'),        // Basic app config file
    require(__DIR__ . '/../../config/web-local.php'),  // Basic app config file
    require(__DIR__ . '/../config/main.php'),          // API config file
    require(__DIR__ . '/../config/main-local.php')     // API config file
);
 
$application = new yii\web\Application($config);
$application->run();

Create file [app]/api/web/robots.txt:

User-Agent: *
Disallow: /

Create file [app]/api/web/css/site.css:

html,
body {
	height: 100%;
}
 
.wrap {
	min-height: 100%;
	height: auto;
	margin: 0 auto -60px;
	padding: 0 0 60px;
}
 
.wrap > .container {
    padding: 70px 15px 20px;
}
 
.footer {
	height: 60px;
	background-color: #f5f5f5;
	border-top: 1px solid #ddd;
	padding-top: 20px;
}
 
.jumbotron {
	text-align: center;
	background-color: transparent;
}
 
.jumbotron .btn {
	font-size: 21px;
	padding: 14px 24px;
}
 
.not-set {
	color: #c55;
	font-style: italic;
}
 
/* add sorting icons to gridview sort links */
a.asc:after, a.desc:after {
	position: relative;
	top: 1px;
	display: inline-block;
	font-family: 'Glyphicons Halflings';
	font-style: normal;
	font-weight: normal;
	line-height: 1;
	padding-left: 5px;
}
 
a.asc:after {
	content: /*"\e113"*/ "\e151";
}
 
a.desc:after {
	content: /*"\e114"*/ "\e152";
}
 
.sort-numerical a.asc:after {
	content: "\e153";
}
 
.sort-numerical a.desc:after {
	content: "\e154";
}
 
.sort-ordinal a.asc:after {
	content: "\e155";
}
 
.sort-ordinal a.desc:after {
	content: "\e156";
}
 
.grid-view th {
    white-space: nowrap;
}
 
.hint-block {
	display: block;
	margin-top: 5px;
	color: #999;
}
 
.error-summary {
	color: #a94442;
	background: #fdf7f7;
	border-left: 3px solid #eed3d7;
	padding: 10px 20px;
	margin: 0 0 15px 0;
}
Using HTTP Basic Authentication

When using the Basic application template: Eg:

...
api
-- config
-- modules
------ v1
---------- controllers
---------- models
------ v2
---------- controllers
---------- models
-- runtime
------ cache
------ debug
------ logs
-- web
------ assets
------ css
assets
commands
config
controllers
mail
migrations
models
runtime
tests
vendor
views
web

[app]/api/config/main-local.php

<?php
 
return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=acme_productionlog',
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8',
        ],
        'mailer' => [
            'class' => 'yii\swiftmailer\Mailer',
            //'viewPath' => '@common/mail',
            'viewPath' => '@app/mail',
        ],
    ],
];

[app]/api/config/main.php

<?php
 
$params = array_merge(
    require(__DIR__ . '/../../config/params.php'),
    require(__DIR__ . '/../../config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);
 
return [
    'id' => 'app-api',
    'basePath' => dirname(__DIR__),      // equivalent to [app]\api
    'bootstrap' => ['log'],
    'modules' => [
        // API v1
        'v1' => [
            'basePath' => '@app/modules/v1',
            'class' => 'api\modules\v1\Module'
        ],
        // API v2
        'v2' => [
            'basePath' => '@app/modules/v2',
            'class' => 'api\modules\v1\Module'
        ]
    ],
    'components' => [        
        'user' => [
            //'identityClass' => 'app\models\User',
            'identityClass' => 'api\modules\v1\models\User',
            'enableAutoLogin' => false,
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
        'urlManager' => [
            'enablePrettyUrl'     => true,
            'enableStrictParsing' => true,
            'showScriptName'      => false,
            'rules' => [
                ['class' => 'yii\rest\UrlRule', 'controller' => 'v1/item-product'],
                [
                    'class'      => 'yii\rest\UrlRule', 
                    'controller' => 'v1/entry',
                    //'tokens'     => [
                    //    '{id}' => '<id:\\w+>'
                    //]
                ],
            ],        
        ],
        'formatter' => [
           'class'           => 'yii\i18n\Formatter',
           'dateFormat'      => 'php:M d, Y',
           //'datetimeFormat'  => 'php:M d, Y H:i:s',
           'timeFormat'      => 'php:H:i:s', 
           'defaultTimeZone' => 'America/New_York'
        ],
        'request' => [
            // Required for JSON input for API
            'parsers' => [
                'application/json' => 'yii\web\JsonParser',
            ]
        ],
        'response' => [
            // Required for JSON output for API
            'formatters' => [
                \yii\web\Response::FORMAT_JSON => [
                    'class'         => 'yii\web\JsonResponseFormatter',
                    'prettyPrint'   => true, //YII_DEBUG, // use "pretty" output in debug mode
                    'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
                ],
            ],
        ],
    ],
    'params' => $params,
];

[app]/api/config/params-local.php

<?php
return [
];

[app]/api/config/params.php

<?php
return [
    'adminEmail' => 'admin@example.com',
];

[app]/api/modules/v1/Module.php

<?php
namespace api\modules\v1;
 
class Module extends \yii\base\Module
{
    public $controllerNamespace = 'api\modules\v1\controllers';
 
    public function init()
    {
        parent::init();
 
        \Yii::$app->user->enableSession = false;        // required for API authentication
 
        //\Yii::$app->user->loginUrl = ['site/login'];  // default to use without API authentication
        \Yii::$app->user->loginUrl = null;              // required for API authentication
 
        // Authentication example: 
        // http://100-token@localhost:8080/production-log/api/web/v1/entries/1
        // http://[access_token]@localhost:8080/production-log/api/web/v1/entries/1
 
    }
}

[app]/api/modules/v1/controllers/EntryController.php

<?php
 
namespace api\modules\v1\controllers;
 
use api\modules\v1\models\Entry;
use yii\rest\ActiveController;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBasicAuth;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;
 
/**
 * EntryController implements the CRUD actions for Entry model.
 */
class EntryController extends ActiveController
{
    public $modelClass = 'api\modules\v1\models\Entry';    
 
    public function behaviors()
    {
        $behaviors = parent::behaviors();
 
        // Required for API authentication
        $behaviors['authenticator'] = [
            // HTTP Basic Authentication
            'class' => HttpBasicAuth::className(),
 
            // Composite Authentication
            //'class' => CompositeAuth::className(),
            //'authMethods' => [
            //    HttpBasicAuth::className(),
            //    HttpBearerAuth::className(),
            //    QueryParamAuth::className(),
            //],
        ];
 
        return $behaviors;
    }
}

[app]/api/modules/v1/models/Entry.php

<?php
namespace api\modules\v1\models;
 
use \yii\db\ActiveRecord;
use \yii\behaviors\TimestampBehavior;
use \yii\behaviors\BlameableBehavior;
use \yii\db\Expression;
 
/**
 * This is the model class for table "entry".
 *
 * @property integer $id
 * @property string $serial_number
 * @property string $product_code
 * @property string $product_data
 * @property string $workstation
 * @property string $created_at
 */
class Entry extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'entry';
    }
 
    /**
     * @inheritdoc
     */
    public static function primaryKey()
    {
        return ['id'];
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            //[['id', 'serial_number', 'product_code', 'workstation', 'product_data', 'created_at'], 'required']
            [['serial_number', 'product_code'], 'required'],
            [['workstation', 'product_data', 'created_at'], 'safe']
        ];
    }
 
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        date_default_timezone_set('America/New_York');
        $formattedCurDateTime = date('Y-m-d H:i:s'); // same format as NOW()
 
        return [
            [
                'class' => TimestampBehavior::className(),
                'createdAtAttribute' => 'created_at', // OR 'create_time', to override default field name
                'updatedAtAttribute' => 'updated_at', // OR 'update_time', to override default field name
                'value' => new \yii\db\Expression('NOW()'),
                //'value' => new \yii\db\Expression($formattedCurDateTime),
            ],
            //[
            //    'class' => BlameableBehavior::className(),
            //    'createdByAttribute' => 'created_by',  // OR 'author_id', to override default field name
            //    'updatedByAttribute' => 'updated_by',  // OR 'updater_id', to override default field name
            //],
        ];
    }
}

[app]/api/modules/v1/models/User.php

<?php
 
//namespace app\models;
namespace api\modules\v1\models;
 
class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
    public $id;
    public $username;
    public $password;
    public $authKey;
    public $accessToken;
 
    private static $users = [
        '100' => [
            'id'          => '100',
            'username'    => 'admin',
            'password'    => 'admin',
            'authKey'     => 'test100key',
            'accessToken' => '100-token',
        ],
        '101' => [
            'id'          => '101',
            'username'    => 'demo',
            'password'    => 'demo',
            'authKey'     => 'test101key',
            //'accessToken' => '101-token',
            // For more security, use a token with a GUID. 
            // Generate GUID: C:\> php -r "echo com_create_guid();"
            'accessToken' => 'B34D5EFE-924B-4BCC-883C-AC22B47E4CD4-token',  
        ],
    ];
 
 
    /**
     * @inheritdoc
     */
    public static function findIdentity($id)
    {
        return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
    }
 
    /**
     * @inheritdoc
     */
    public static function findIdentityByAccessToken($token, $type = null)
    {
        foreach (self::$users as $user) {
            if ($user['accessToken'] === $token) {
                return new static($user);
            }
        }
 
        return null;
    }
 
    /**
     * Finds user by username
     *
     * @param string $username
     * @return static|null
     */
    public static function findByUsername($username)
    {
        foreach (self::$users as $user) {
            if (strcasecmp($user['username'], $username) === 0) {
                return new static($user);
            }
        }
 
        return null;
    }
 
    /**
     * @inheritdoc
     */
    public function getId()
    {
        return $this->id;
    }
 
    /**
     * @inheritdoc
     */
    public function getAuthKey()
    {
        return $this->authKey;
    }
 
    /**
     * @inheritdoc
     */
    public function validateAuthKey($authKey)
    {
        return $this->authKey === $authKey;
    }
 
    /**
     * Validates password
     *
     * @param string $password password to validate
     * @return boolean if password provided is valid for current user
     */
    public function validatePassword($password)
    {
        return $this->password === $password;
    }
}

Add additional files in [app]/api/web:

  • .htaccess
  • index.php
  • robots.txt
  • css/site.css

[app]/api/web/index.php

<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
 
require(__DIR__ . '/../../vendor/autoload.php');
require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
//require(__DIR__ . '/../../config/aliases.php');
 
$config = yii\helpers\ArrayHelper::merge(
    //require(__DIR__ . '/../../config/main.php'),       // Advanced app config file
    //require(__DIR__ . '/../../config/main-local.php'), // Advanced app config file
    require(__DIR__ . '/../../config/web.php'),          // Basic app config file
    require(__DIR__ . '/../../config/web-local.php'),    // Basic app config file
    require(__DIR__ . '/../config/main.php'),            // API config file
    require(__DIR__ . '/../config/main-local.php')       // API config file
);
 
$application = new yii\web\Application($config);
$application->run();
Test API

Call the API using the following URLs (assuming acme is the application name):

Use CURL to test API calls. Eg: In Windows:

Create record

C:\> curl -i -H "Accept:application/json" 
             -H "Content-Type:application/json" 
             -X POST 
             "http://123-token@localhost/acme/api/web/v1/entries" 
             -d "{\"serial_number\": \"0\", \"product_code\": \"prod_AcmeJewels\", 
                 \"product_data\": \"<data>None</data>\", \"workstation\": \"John-laptop\"}"

NOTE: In Windows, for JSON data, always use double quotes (no single quotes), and escape them with a backslash if any quotes are embedded in a string.

List all records

C:\> curl -i -H "Accept:application/json" "http://123-token@localhost/acme/api/web/v1/entries"
Modifying JSON Response

There are several ways to impact the formatting of the JSON (or whatever method used) reponse.

Using configuration in [app]/config/web.php (Basic app), [app]/common/config/main.php (Advanced app), or [app]/api/config/main.php:

...
$config = [
    //...
    'components' => [
        //...
        'response' => [
            // Required for JSON output for API
            'formatters' => [
                \yii\web\Response::FORMAT_JSON => [
                    'class'         => 'yii\web\JsonResponseFormatter',
                    'prettyPrint'   => true, //YII_DEBUG, // use "pretty" output in debug mode
                    'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
                ],
            ],
        ],
        //...
    ],
    'params' => $params,
];
...

Using modified JSON response after a search. Edit your model to include:

<?php
 
namespace app\models;
use Yii;
 
class MyModel extends \yii\db\ActiveRecord
{
    //...
    public function afterFind() 
    {
        $this->data = $this->sanitizeStringForJson($this->data);
        parent::afterFind();
    }
 
    //-------------------------------------------------------------------------
    // description: Strip extra characters, and quote string for JSON use.
    //              Usage: Tool::sanitizeStringForJson($str);  
    // parameters : $str
    // return     : $str (formatted string)
    //-------------------------------------------------------------------------
    private function sanitizeStringForJson($str) 
    {
        $str = str_replace("\\", "\\\\",  $str);
        $str = str_replace('/', "\\/",    $str);
        $str = str_replace('"', "\\".'"', $str);
        $str = str_replace("\b", "\\b",   $str);
        $str = str_replace("\t", "\\t",   $str);
        $str = str_replace("\n", "\\n",   $str);
        $str = str_replace("\f", "\\f",   $str);
        $str = str_replace("\r", "\\r",   $str);
        $str = str_replace("\u", "\\u",   $str);
 
        return '"'.$str.'"';
    }
}
?>

See also:

Override an Action

In an API controller, you can override an action by disabling the default one, and creating a comparable action. Eg:

//...
use app\models\Entry;
 
class ApiEntryController extends ActiveController
{
    //...
 
    public function actions()
    {
        //$actions = parent::actions();
        //// disable actions if needed
        //unset($actions['delete'], $actions['create']);  // disable the "delete" and "create" actions
        //return $actions;
 
        return array_merge(parent::actions(), [
            'create' => null, // Disable create, to override it later with actionCreate()
            'delete' => null, // Disable delete, to block access to it
            //...
        ]);
    }
 
    // Override default Create action with this implementation
    public function actionCreate()
    {
        $model = new Entry();
 
        //if ($model->load(Yii::$app->request->post())) {
        if ($model->load(Yii::$app->getRequest()->getBodyParams(), '')) {
            $model->product_data_hash = md5($model->product_data);  // massage some data before saving, if needed
            if ($model->save()) {
                return $model;
            } else {
                throw new ServerErrorHttpException('Failed to save the Entry object for unknown reason.');
            }
        }
        return $model;
    }
}    
Add a Custom Action

In the API controller, add the action and findModel() functions. Eg:

//...
use app\models\Entry;
use app\models\EntrySearch;
use app\models\Raw;
 
class ApiEntryController extends ActiveController
{
    //...
 
    // Flag the specified record as "Processed"
    public function actionFlagProcessed($id)
    {
        $model = $this->findModel($id);
 
        $model->processed = true;
        if ($model->save()) {
            return $model;
        } else {
            throw new ServerErrorHttpException('Failed to save the Entry object for unknown reason.');
        }
        return $model;
    }
 
    /**
     * Finds the Raw model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Raw the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Raw::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}    

Add rules to the urlManager component in the [app]/config/web.php (Basic app), or [app]/common/config/main.php (Advanced app). Eg:

//...
$config = [
    'id' => 'basic',
    'basePath'   => dirname(__DIR__),
    'bootstrap'  => ['log'],
    'components' => [
        //...
        'urlManager' => [
            'enablePrettyUrl'     => true,
            'showScriptName'      => false,
            'enableStrictParsing' => false,
            'rules' => [
                [
                    'class'         => 'yii\rest\UrlRule', 
                    'controller'    => 'api-entry',  // or 'v1/api-entry' when in /api/modules/v1/controllers/api-entry
                    'extraPatterns' => [
                        'GET flag-printed/<id>'   => 'flag-printed',    // action flag-printed
                        'GET flag-processed/<id>' => 'flag-processed',
                    ],
                ],
            ]
        ],
    ],
    'params' => $params,
];  
//...      
References