├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── config ├── config.php ├── console.php ├── db.php └── params.php ├── controllers └── SiteController.php ├── docker-compose.yml ├── helpers ├── AuthMethodsFromParamsHelper.php └── BehaviorsFromParamsHelper.php ├── migrations ├── m141022_115823_create_user_table.php ├── m180221_085153_create_post_table.php └── m200609_112354_create_access_token_table.php ├── models ├── AccessToken.php ├── Post.php ├── Status.php ├── User.php └── UserIdentity.php ├── modules └── v1 │ ├── controllers │ ├── DefaultController.php │ └── PostController.php │ └── v1.php ├── vendor └── .gitignore ├── web ├── .htaccess └── index.php ├── yii └── yii.bat /.gitignore: -------------------------------------------------------------------------------- 1 | # yii console command 2 | # /yii 3 | 4 | # phpstorm project files 5 | .idea 6 | 7 | # netbeans project files 8 | nbproject 9 | 10 | # zend studio for eclipse project files 11 | .buildpath 12 | .project 13 | .settings 14 | 15 | # windows thumbnail cache 16 | Thumbs.db 17 | 18 | _protected/config/db.php 19 | 20 | # composer itself is not needed 21 | composer.phar 22 | 23 | # Mac DS_Store Files 24 | .DS_Store 25 | 26 | # phpunit itself is not needed 27 | phpunit.phar 28 | # local phpunit config 29 | /phpunit.xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright 2020 @hoaaah 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

Yii 2 REST API Template

6 |
7 |

8 | 9 | Yii2 REST API Template 10 | ------------------- 11 | This is a a REST API TEMPLATE with Yii2. This template use [Yii2-Micro](https://github.com/hoaaah/yii2-micro) approach so it will be lightweight and easy to deploy. 12 | 13 | 14 | # Installation 15 | 16 | The preferred way to install this template is through [composer](http://getcomposer.org/download/). 17 | 18 | Either run 19 | 20 | ```bash 21 | composer create-project --prefer-dist hoaaah/yii2-rest-api-template [app_name] 22 | ``` 23 | 24 | Setup your database configuration from `config/db.php`. Create your database because this template will not create it for you :) 25 | 26 | ```php 27 | [ 30 | 'db' => [ 31 | 'class' => 'yii\db\Connection', 32 | 'dsn' => 'mysql:host=localhost;dbname=your_db_name', 33 | 'username' => 'root', 34 | 'password' => '', 35 | 'charset' => 'utf8', 36 | ], 37 | ], 38 | ]; 39 | 40 | ``` 41 | 42 | Then run migration to create table in selected database. 43 | 44 | ```bash 45 | yii migrate 46 | ``` 47 | 48 | # Directory Structure 49 | Since this template use MicroFramework approach, directory structure might be a little bit different from Yii2. 50 | 51 | config/ contains application configurations 52 | controllers/ contains Web controller classes 53 | migration/ contains list of your migration files 54 | models/ contains model classes 55 | modules/ contains your rest-api versioning (based on modules) 56 | vendor/ contains dependent 3rd-party packages 57 | web/ contains the entry script and Web resources 58 | 59 | This template use modules as versioning pattern. Every version of API saved in a module. This template already have v1 module, so it means if consumer want to use v1 API, it can access `https://your-api-url/v1/endpoint`. 60 | 61 | 62 | # API Scenario 63 | ## Supported Authentication 64 | This template support 3 most used authentication. (Actually it's not me who make it, Yii2 already support it all :D ). 65 | 66 | 1. HTTP Basic Auth: the access token is sent as the username. This should only be used when an access token can be safely stored on the API consumer side. For example, the API consumer is a program running on a server. 67 | 2. Query parameter: the access token is sent as a query parameter in the API URL, e.g., https://example.com/users?access-token=xxxxxxxx. Because most Web servers will keep query parameters in server logs, this approach should be mainly used to serve JSONP requests which cannot use HTTP headers to send access tokens. 68 | 3. OAuth 2: the access token is obtained by the consumer from an authorization server and sent to the API server via HTTP Bearer Tokens, according to the OAuth2 protocol. 69 | 70 | ## Global Configuration of AuthMethods and RateLimiter 71 | This template provide global configuration to set your application supported authMethods. You can find global configuration from `app\config\params.php`. Set your supported authMethods and RateLimiter from this file. 72 | 73 | ```php 74 | return [ 75 | 'useHttpBasicAuth' => true, 76 | 'useHttpBearerAuth' => true, 77 | 'useQueryParamAuth' => true, 78 | 'useRateLimiter' => false, 79 | ]; 80 | ``` 81 | 82 | Example use in behaviors looks like this 83 | 84 | ```php 85 | use app\helpers\BehaviorsFromParamsHelper; 86 | use yii\rest\ActiveController; 87 | 88 | class PostController extends ActiveController 89 | { 90 | public $modelClass = 'app\models\Post'; 91 | 92 | public function behaviors() 93 | { 94 | $behaviors = parent::behaviors(); 95 | $behaviors = BehaviorsFromParamsHelper::behaviors($behaviors); 96 | // if you need other behaviors method use like this 97 | // $behaviors['otherMethods'] = $value; 98 | return $behaviors; 99 | } 100 | } 101 | ``` 102 | 103 | ### Ratelimiter 104 | To enable your ratelimiter configuration, please follow official guide from [Yii documentation](https://www.yiiframework.com/doc/guide/2.0/en/rest-rate-limiting). 105 | 106 | ## Auth Scenario 107 | This template already have basic endpoint that you can use to start your REST-API. Such as: 108 | 109 | Endpoint | Type |Usage 110 | ---------|------|----- 111 | https://YOUR-API-URL/ | GET| list all post created 112 | https://YOUR-API-URL/view?id={id} | GET| View a post 113 | https://YOUR-API-URL/login | POST | Login with username and password 114 | https://YOUR-API-URL/signup | POST | Signup with username, email and password 115 | https://YOUR-API-URL/v1/post | GET | List all post created 116 | https://YOUR-API-URL/v1/post/create | POST | Create a new post (title, body) 117 | https://YOUR-API-URL/v1/post/update?id={id} | PUT / PATCH | Update a post (title, body) 118 | https://YOUR-API-URL/v1/post/delete?id={id} | DELETE | Delete a post 119 | https://YOUR-API-URL/v1/post/view?id={id} | GET | View a post 120 | 121 | ## Access Token Management 122 | This application manage token via access_token table. Access Token have certain expiration based on $tokenExpiration value. Default Token Expiration are in seconds. 123 | 124 | ```php 125 | public $tokenExpiration = 60 * 24 * 365; // in seconds 126 | ``` 127 | 128 | In certain case you want to make a token expire before given tokenExpiration. Use ```expireThisToken()``` method to achieve it. 129 | ```php 130 | $accessToken = AccessToken::findOne(['token' => $token]); 131 | $accessToken->expireThisToken(); 132 | ``` 133 | 134 | Or you want to make all tokens from certain user expire, use ```makeAllUserTokenExpiredByUserId($userId)``` method to achieve it. 135 | ```php 136 | $user = Yii::$app->user->identity; // or User::findOne($id) 137 | AccessToken::makeAllUserTokenExpiredByUserId($user->id); 138 | ``` 139 | 140 | ## API versioning 141 | This template give you versioning scenario based on module application. In Yii2 a module are self-contained software units that consist of model, views, controllers and other supporting components. This template already have v1 module, it means all of endpoint for API v1 created in this module. When you publish a new API version (that break backward compatibility / BBC), you can create a new module. For more information create a module, you can visit this [Yii2 Guide on Creating Module](https://www.yiiframework.com/doc/guide/2.0/en/structure-modules). 142 | 143 | 144 | # TODO 145 | Feel free to contribute if you have any idea. 146 | - [x] Rest API Template 147 | - [x] Login and signup in SiteController 148 | - [x] Example of versioning and Blog Scenario 149 | - [x] Authentication Type from params 150 | - [x] Rate Limit from params 151 | - [x] Change auth_key for every login 152 | - [x] Auth_key have expiration 153 | - [x] each auth_key have application token 154 | 155 | 156 | # Creator 157 | 158 | This Template was created by and is maintained by **[Heru Arief Wijaya](http://belajararief.com/)**. 159 | 160 | * https://twitter.com/hoaaah 161 | * https://github.com/hoaaah 162 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hoaaah/yii2-rest-api-template", 3 | "description": "REST API Template with Yii2", 4 | "license": "Apache-2.0", 5 | "require": { 6 | "yiisoft/yii2": "~2.0.0" 7 | }, 8 | "authors": [ 9 | { 10 | "name": "Arief Wijaya", 11 | "email": "hoaaah.arief@gmail.com" 12 | } 13 | ], 14 | "repositories": [ 15 | { 16 | "type": "composer", 17 | "url": "https://asset-packagist.org" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "32d871bdfcd4ca82767781f505bbd560", 8 | "packages": [ 9 | { 10 | "name": "bower-asset/inputmask", 11 | "version": "5.0.9", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/RobinHerbots/Inputmask.git", 15 | "reference": "310a33557e2944daf86d5946a5e8c82b9118f8f7" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/310a33557e2944daf86d5946a5e8c82b9118f8f7", 20 | "reference": "310a33557e2944daf86d5946a5e8c82b9118f8f7" 21 | }, 22 | "require": { 23 | "bower-asset/jquery": ">=1.7" 24 | }, 25 | "type": "bower-asset", 26 | "license": [ 27 | "http://opensource.org/licenses/mit-license.php" 28 | ] 29 | }, 30 | { 31 | "name": "bower-asset/jquery", 32 | "version": "3.7.1", 33 | "source": { 34 | "type": "git", 35 | "url": "https://github.com/jquery/jquery-dist.git", 36 | "reference": "fde1f76e2799dd877c176abde0ec836553246991" 37 | }, 38 | "dist": { 39 | "type": "zip", 40 | "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/fde1f76e2799dd877c176abde0ec836553246991", 41 | "reference": "fde1f76e2799dd877c176abde0ec836553246991" 42 | }, 43 | "type": "bower-asset", 44 | "license": [ 45 | "MIT" 46 | ] 47 | }, 48 | { 49 | "name": "bower-asset/punycode", 50 | "version": "v2.3.1", 51 | "source": { 52 | "type": "git", 53 | "url": "https://github.com/mathiasbynens/punycode.js.git", 54 | "reference": "9e1b2cda98d215d3a73fcbfe93c62e021f4ba768" 55 | }, 56 | "dist": { 57 | "type": "zip", 58 | "url": "https://api.github.com/repos/mathiasbynens/punycode.js/zipball/9e1b2cda98d215d3a73fcbfe93c62e021f4ba768", 59 | "reference": "9e1b2cda98d215d3a73fcbfe93c62e021f4ba768" 60 | }, 61 | "type": "bower-asset" 62 | }, 63 | { 64 | "name": "bower-asset/yii2-pjax", 65 | "version": "2.0.8", 66 | "source": { 67 | "type": "git", 68 | "url": "git@github.com:yiisoft/jquery-pjax.git", 69 | "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" 70 | }, 71 | "dist": { 72 | "type": "zip", 73 | "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/a9298d57da63d14a950f1b94366a864bc62264fb", 74 | "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" 75 | }, 76 | "require": { 77 | "bower-asset/jquery": ">=1.8" 78 | }, 79 | "type": "bower-asset", 80 | "license": [ 81 | "MIT" 82 | ] 83 | }, 84 | { 85 | "name": "cebe/markdown", 86 | "version": "1.2.1", 87 | "source": { 88 | "type": "git", 89 | "url": "https://github.com/cebe/markdown.git", 90 | "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86" 91 | }, 92 | "dist": { 93 | "type": "zip", 94 | "url": "https://api.github.com/repos/cebe/markdown/zipball/9bac5e971dd391e2802dca5400bbeacbaea9eb86", 95 | "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86", 96 | "shasum": "" 97 | }, 98 | "require": { 99 | "lib-pcre": "*", 100 | "php": ">=5.4.0" 101 | }, 102 | "require-dev": { 103 | "cebe/indent": "*", 104 | "facebook/xhprof": "*@dev", 105 | "phpunit/phpunit": "4.1.*" 106 | }, 107 | "bin": [ 108 | "bin/markdown" 109 | ], 110 | "type": "library", 111 | "extra": { 112 | "branch-alias": { 113 | "dev-master": "1.2.x-dev" 114 | } 115 | }, 116 | "autoload": { 117 | "psr-4": { 118 | "cebe\\markdown\\": "" 119 | } 120 | }, 121 | "notification-url": "https://packagist.org/downloads/", 122 | "license": [ 123 | "MIT" 124 | ], 125 | "authors": [ 126 | { 127 | "name": "Carsten Brandt", 128 | "email": "mail@cebe.cc", 129 | "homepage": "http://cebe.cc/", 130 | "role": "Creator" 131 | } 132 | ], 133 | "description": "A super fast, highly extensible markdown parser for PHP", 134 | "homepage": "https://github.com/cebe/markdown#readme", 135 | "keywords": [ 136 | "extensible", 137 | "fast", 138 | "gfm", 139 | "markdown", 140 | "markdown-extra" 141 | ], 142 | "support": { 143 | "issues": "https://github.com/cebe/markdown/issues", 144 | "source": "https://github.com/cebe/markdown" 145 | }, 146 | "time": "2018-03-26T11:24:36+00:00" 147 | }, 148 | { 149 | "name": "ezyang/htmlpurifier", 150 | "version": "v4.17.0", 151 | "source": { 152 | "type": "git", 153 | "url": "https://github.com/ezyang/htmlpurifier.git", 154 | "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" 155 | }, 156 | "dist": { 157 | "type": "zip", 158 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", 159 | "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", 160 | "shasum": "" 161 | }, 162 | "require": { 163 | "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" 164 | }, 165 | "require-dev": { 166 | "cerdic/css-tidy": "^1.7 || ^2.0", 167 | "simpletest/simpletest": "dev-master" 168 | }, 169 | "suggest": { 170 | "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", 171 | "ext-bcmath": "Used for unit conversion and imagecrash protection", 172 | "ext-iconv": "Converts text to and from non-UTF-8 encodings", 173 | "ext-tidy": "Used for pretty-printing HTML" 174 | }, 175 | "type": "library", 176 | "autoload": { 177 | "files": [ 178 | "library/HTMLPurifier.composer.php" 179 | ], 180 | "psr-0": { 181 | "HTMLPurifier": "library/" 182 | }, 183 | "exclude-from-classmap": [ 184 | "/library/HTMLPurifier/Language/" 185 | ] 186 | }, 187 | "notification-url": "https://packagist.org/downloads/", 188 | "license": [ 189 | "LGPL-2.1-or-later" 190 | ], 191 | "authors": [ 192 | { 193 | "name": "Edward Z. Yang", 194 | "email": "admin@htmlpurifier.org", 195 | "homepage": "http://ezyang.com" 196 | } 197 | ], 198 | "description": "Standards compliant HTML filter written in PHP", 199 | "homepage": "http://htmlpurifier.org/", 200 | "keywords": [ 201 | "html" 202 | ], 203 | "support": { 204 | "issues": "https://github.com/ezyang/htmlpurifier/issues", 205 | "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" 206 | }, 207 | "time": "2023-11-17T15:01:25+00:00" 208 | }, 209 | { 210 | "name": "paragonie/random_compat", 211 | "version": "v9.99.100", 212 | "source": { 213 | "type": "git", 214 | "url": "https://github.com/paragonie/random_compat.git", 215 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" 216 | }, 217 | "dist": { 218 | "type": "zip", 219 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", 220 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", 221 | "shasum": "" 222 | }, 223 | "require": { 224 | "php": ">= 7" 225 | }, 226 | "require-dev": { 227 | "phpunit/phpunit": "4.*|5.*", 228 | "vimeo/psalm": "^1" 229 | }, 230 | "suggest": { 231 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 232 | }, 233 | "type": "library", 234 | "notification-url": "https://packagist.org/downloads/", 235 | "license": [ 236 | "MIT" 237 | ], 238 | "authors": [ 239 | { 240 | "name": "Paragon Initiative Enterprises", 241 | "email": "security@paragonie.com", 242 | "homepage": "https://paragonie.com" 243 | } 244 | ], 245 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 246 | "keywords": [ 247 | "csprng", 248 | "polyfill", 249 | "pseudorandom", 250 | "random" 251 | ], 252 | "support": { 253 | "email": "info@paragonie.com", 254 | "issues": "https://github.com/paragonie/random_compat/issues", 255 | "source": "https://github.com/paragonie/random_compat" 256 | }, 257 | "time": "2020-10-15T08:29:30+00:00" 258 | }, 259 | { 260 | "name": "yiisoft/yii2", 261 | "version": "2.0.50", 262 | "source": { 263 | "type": "git", 264 | "url": "https://github.com/yiisoft/yii2-framework.git", 265 | "reference": "a90b6638cce84e78ed8f681a5cad4a14c76464b4" 266 | }, 267 | "dist": { 268 | "type": "zip", 269 | "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/a90b6638cce84e78ed8f681a5cad4a14c76464b4", 270 | "reference": "a90b6638cce84e78ed8f681a5cad4a14c76464b4", 271 | "shasum": "" 272 | }, 273 | "require": { 274 | "bower-asset/inputmask": "^5.0.8 ", 275 | "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", 276 | "bower-asset/punycode": "^2.2", 277 | "bower-asset/yii2-pjax": "~2.0.1", 278 | "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", 279 | "ext-ctype": "*", 280 | "ext-mbstring": "*", 281 | "ezyang/htmlpurifier": "^4.17", 282 | "lib-pcre": "*", 283 | "paragonie/random_compat": ">=1", 284 | "php": ">=7.3.0", 285 | "yiisoft/yii2-composer": "~2.0.4" 286 | }, 287 | "bin": [ 288 | "yii" 289 | ], 290 | "type": "library", 291 | "extra": { 292 | "branch-alias": { 293 | "dev-master": "2.0.x-dev" 294 | } 295 | }, 296 | "autoload": { 297 | "psr-4": { 298 | "yii\\": "" 299 | } 300 | }, 301 | "notification-url": "https://packagist.org/downloads/", 302 | "license": [ 303 | "BSD-3-Clause" 304 | ], 305 | "authors": [ 306 | { 307 | "name": "Qiang Xue", 308 | "email": "qiang.xue@gmail.com", 309 | "homepage": "https://www.yiiframework.com/", 310 | "role": "Founder and project lead" 311 | }, 312 | { 313 | "name": "Alexander Makarov", 314 | "email": "sam@rmcreative.ru", 315 | "homepage": "https://rmcreative.ru/", 316 | "role": "Core framework development" 317 | }, 318 | { 319 | "name": "Maurizio Domba", 320 | "homepage": "http://mdomba.info/", 321 | "role": "Core framework development" 322 | }, 323 | { 324 | "name": "Carsten Brandt", 325 | "email": "mail@cebe.cc", 326 | "homepage": "https://www.cebe.cc/", 327 | "role": "Core framework development" 328 | }, 329 | { 330 | "name": "Timur Ruziev", 331 | "email": "resurtm@gmail.com", 332 | "homepage": "http://resurtm.com/", 333 | "role": "Core framework development" 334 | }, 335 | { 336 | "name": "Paul Klimov", 337 | "email": "klimov.paul@gmail.com", 338 | "role": "Core framework development" 339 | }, 340 | { 341 | "name": "Dmitry Naumenko", 342 | "email": "d.naumenko.a@gmail.com", 343 | "role": "Core framework development" 344 | }, 345 | { 346 | "name": "Boudewijn Vahrmeijer", 347 | "email": "info@dynasource.eu", 348 | "homepage": "http://dynasource.eu", 349 | "role": "Core framework development" 350 | } 351 | ], 352 | "description": "Yii PHP Framework Version 2", 353 | "homepage": "https://www.yiiframework.com/", 354 | "keywords": [ 355 | "framework", 356 | "yii2" 357 | ], 358 | "support": { 359 | "forum": "https://forum.yiiframework.com/", 360 | "irc": "ircs://irc.libera.chat:6697/yii", 361 | "issues": "https://github.com/yiisoft/yii2/issues?state=open", 362 | "source": "https://github.com/yiisoft/yii2", 363 | "wiki": "https://www.yiiframework.com/wiki" 364 | }, 365 | "funding": [ 366 | { 367 | "url": "https://github.com/yiisoft", 368 | "type": "github" 369 | }, 370 | { 371 | "url": "https://opencollective.com/yiisoft", 372 | "type": "open_collective" 373 | }, 374 | { 375 | "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2", 376 | "type": "tidelift" 377 | } 378 | ], 379 | "time": "2024-05-30T17:23:31+00:00" 380 | }, 381 | { 382 | "name": "yiisoft/yii2-composer", 383 | "version": "2.0.10", 384 | "source": { 385 | "type": "git", 386 | "url": "https://github.com/yiisoft/yii2-composer.git", 387 | "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510" 388 | }, 389 | "dist": { 390 | "type": "zip", 391 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/94bb3f66e779e2774f8776d6e1bdeab402940510", 392 | "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510", 393 | "shasum": "" 394 | }, 395 | "require": { 396 | "composer-plugin-api": "^1.0 | ^2.0" 397 | }, 398 | "require-dev": { 399 | "composer/composer": "^1.0 | ^2.0@dev", 400 | "phpunit/phpunit": "<7" 401 | }, 402 | "type": "composer-plugin", 403 | "extra": { 404 | "class": "yii\\composer\\Plugin", 405 | "branch-alias": { 406 | "dev-master": "2.0.x-dev" 407 | } 408 | }, 409 | "autoload": { 410 | "psr-4": { 411 | "yii\\composer\\": "" 412 | } 413 | }, 414 | "notification-url": "https://packagist.org/downloads/", 415 | "license": [ 416 | "BSD-3-Clause" 417 | ], 418 | "authors": [ 419 | { 420 | "name": "Qiang Xue", 421 | "email": "qiang.xue@gmail.com" 422 | }, 423 | { 424 | "name": "Carsten Brandt", 425 | "email": "mail@cebe.cc" 426 | } 427 | ], 428 | "description": "The composer plugin for Yii extension installer", 429 | "keywords": [ 430 | "composer", 431 | "extension installer", 432 | "yii2" 433 | ], 434 | "support": { 435 | "forum": "http://www.yiiframework.com/forum/", 436 | "irc": "irc://irc.freenode.net/yii", 437 | "issues": "https://github.com/yiisoft/yii2-composer/issues", 438 | "source": "https://github.com/yiisoft/yii2-composer", 439 | "wiki": "http://www.yiiframework.com/wiki/" 440 | }, 441 | "funding": [ 442 | { 443 | "url": "https://github.com/yiisoft", 444 | "type": "github" 445 | }, 446 | { 447 | "url": "https://opencollective.com/yiisoft", 448 | "type": "open_collective" 449 | }, 450 | { 451 | "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-composer", 452 | "type": "tidelift" 453 | } 454 | ], 455 | "time": "2020-06-24T00:04:01+00:00" 456 | } 457 | ], 458 | "packages-dev": [], 459 | "aliases": [], 460 | "minimum-stability": "stable", 461 | "stability-flags": [], 462 | "prefer-stable": false, 463 | "prefer-lowest": false, 464 | "platform": [], 465 | "platform-dev": [], 466 | "plugin-api-version": "2.6.0" 467 | } 468 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | 'micro-app', 17 | // the basePath of the application will be the `micro-app` directory 18 | 'basePath' => dirname(__DIR__), 19 | 'modules' => [ 20 | 'v1' => [ 21 | 'class' => 'app\modules\v1\v1', 22 | ], 23 | ], 24 | // this is where the application will find all controllers 25 | 'controllerNamespace' => 'app\controllers', 26 | // set an alias to enable autoloading of classes from the 'micro' namespace 27 | 'aliases' => [ 28 | '@app' => __DIR__.'/../', 29 | ], 30 | 'components' => [ 31 | 'urlManager' => [ 32 | 'class' => 'yii\web\UrlManager', 33 | 'enablePrettyUrl' => true, 34 | 'showScriptName' => false, 35 | 'rules' => [ 36 | '' => 'site/', 37 | ], 38 | ], 39 | 'user' => [ 40 | 'identityClass' => 'app\models\UserIdentity', 41 | 'enableAutoLogin' => false, 42 | 'enableSession' => false, 43 | 'loginUrl' => null, 44 | ], 45 | 'request' => [ 46 | 'parsers' => [ 47 | 'application/json' => 'yii\web\JsonParser', 48 | ], 49 | 'enableCsrfCookie' => false, 50 | ], 51 | 'db' => $db, 52 | ], 53 | 'params' => $params, 54 | ]; -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 'basic-console', 8 | 'basePath' => dirname(__DIR__), 9 | 'bootstrap' => ['log'], 10 | 'controllerNamespace' => 'app\console\controllers', 11 | 'controllerMap' => [ 12 | 'migrate' => [ 13 | 'class' => 'yii\console\controllers\MigrateController', 14 | 'migrationPath' => '@app/migrations', 15 | ], 16 | ], 17 | 'components' => [ 18 | 'authManager' => [ 19 | 'class' => 'yii\rbac\DbManager', 20 | ], 21 | 'cache' => [ 22 | 'class' => 'yii\caching\FileCache', 23 | ], 24 | 'user' => [ 25 | 'class' => 'app\models\UserIdentity', 26 | 'enableAutoLogin' => false, 27 | 'enableSession' => false, 28 | 'loginUrl' => null, 29 | ], 30 | 'log' => [ 31 | 'targets' => [ 32 | [ 33 | 'class' => 'yii\log\FileTarget', 34 | 'levels' => ['error', 'warning'], 35 | ], 36 | ], 37 | ], 38 | 'db' => $db, 39 | ], 40 | 'params' => $params, 41 | ]; 42 | 43 | 44 | return $config; -------------------------------------------------------------------------------- /config/db.php: -------------------------------------------------------------------------------- 1 | 'yii\db\Connection', 6 | 'dsn' => 'mysql:host=localhost;dbname=yii2-rest-api', 7 | 'username' => 'root', 8 | 'password' => '', 9 | 'charset' => 'utf8', 10 | ]; -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | true, 6 | 'useHttpBearerAuth' => true, 7 | 'useQueryParamAuth' => true, 8 | 9 | /** 10 | * use rate limiter for user 11 | * you must modified your UserIdentity class, follow this guidelines for complete guide 12 | * https://www.yiiframework.com/doc/guide/2.0/en/rest-rate-limiting 13 | */ 14 | 'useRateLimiter' => false, 15 | ]; 16 | -------------------------------------------------------------------------------- /controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | ['POST'], 24 | 'login' => ['POST'], 25 | ]; 26 | } 27 | 28 | public function actionIndex() 29 | { 30 | $post = Post::find()->all(); 31 | return [ 32 | 'status' => Status::STATUS_OK, 33 | 'message' => 'Hello :)', 34 | 'data' => $post 35 | ]; 36 | } 37 | 38 | 39 | public function actionView($id) 40 | { 41 | $post = Post::findOne($id); 42 | return [ 43 | 'status' => Status::STATUS_FOUND, 44 | 'message' => 'Data Found', 45 | 'data' => $post 46 | ]; 47 | } 48 | 49 | public function actionSignup() 50 | { 51 | $model = new User(); 52 | $params = Yii::$app->request->post(); 53 | if(!$params) { 54 | Yii::$app->response->statusCode = Status::STATUS_BAD_REQUEST; 55 | return [ 56 | 'status' => Status::STATUS_BAD_REQUEST, 57 | 'message' => "Need username, password, and email.", 58 | 'data' => '' 59 | ]; 60 | } 61 | 62 | 63 | $model->username = $params['username']; 64 | $model->email = $params['email']; 65 | 66 | $model->setPassword($params['password']); 67 | $model->generateAuthKey(); 68 | $model->status = User::STATUS_ACTIVE; 69 | 70 | if ($model->save()) { 71 | Yii::$app->response->statusCode = Status::STATUS_CREATED; 72 | $response['isSuccess'] = 201; 73 | $response['message'] = 'You are now a member!'; 74 | $response['user'] = \app\models\User::findByUsername($model->username); 75 | return [ 76 | 'status' => Status::STATUS_CREATED, 77 | 'message' => 'You are now a member', 78 | 'data' => User::findByUsername($model->username), 79 | ]; 80 | } else { 81 | Yii::$app->response->statusCode = Status::STATUS_BAD_REQUEST; 82 | $model->getErrors(); 83 | $response['hasErrors'] = $model->hasErrors(); 84 | $response['errors'] = $model->getErrors(); 85 | return [ 86 | 'status' => Status::STATUS_BAD_REQUEST, 87 | 'message' => 'Error saving data!', 88 | 'data' => [ 89 | 'hasErrors' => $model->hasErrors(), 90 | 'getErrors' => $model->getErrors(), 91 | ] 92 | ]; 93 | } 94 | } 95 | 96 | public function actionLogin() 97 | { 98 | $params = Yii::$app->request->post(); 99 | if(empty($params['username']) || empty($params['password'])) return [ 100 | 'status' => Status::STATUS_BAD_REQUEST, 101 | 'message' => "Need username and password.", 102 | 'data' => '' 103 | ]; 104 | 105 | $user = User::findByUsername($params['username']); 106 | 107 | if ($user->validatePassword($params['password'])) { 108 | if(isset($params['consumer'])) $user->consumer = $params['consumer']; 109 | if(isset($params['access_given'])) $user->access_given = $params['access_given']; 110 | 111 | Yii::$app->response->statusCode = Status::STATUS_FOUND; 112 | $user->generateAuthKey(); 113 | $user->save(); 114 | return [ 115 | 'status' => Status::STATUS_FOUND, 116 | 'message' => 'Login Succeed, save your token', 117 | 'data' => [ 118 | 'id' => $user->username, 119 | 'token' => $user->auth_key, 120 | 'email' => $user['email'], 121 | ] 122 | ]; 123 | } else { 124 | Yii::$app->response->statusCode = Status::STATUS_UNAUTHORIZED; 125 | return [ 126 | 'status' => Status::STATUS_UNAUTHORIZED, 127 | 'message' => 'Username and Password not found. Check Again!', 128 | 'data' => '' 129 | ]; 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | php: 4 | image: yiisoftware/yii2-php:7.3-apache 5 | volumes: 6 | - ~/.composer-docker/cache:/root/.composer/cache:delegated 7 | - ./:/app:delegated 8 | ports: 9 | - '8000:80' -------------------------------------------------------------------------------- /helpers/AuthMethodsFromParamsHelper.php: -------------------------------------------------------------------------------- 1 | getAuthMethods(); 34 | } 35 | 36 | /** 37 | * Give array of authMethods from params.php 38 | * the result of $this->getAuthMethods would be like following 39 | * ```php 40 | * [ 41 | * Http:BasicAuth::class, 42 | * HttpBearerAuth::class, 43 | * QueryParamAuth::class, 44 | * ] 45 | * ``` 46 | * 47 | * @return array the array representation of the object 48 | */ 49 | private function getAuthMethods(){ 50 | $authMethodsArray = null; 51 | if(Yii::$app->params['useHttpBasicAuth']) $authMethodsArray[] = HttpBasicAuth::class; 52 | if(Yii::$app->params['useHttpBearerAuth']) $authMethodsArray[] = HttpBearerAuth::class; 53 | if(Yii::$app->params['useQueryParamAuth']) $authMethodsArray[] = QueryParamAuth::class; 54 | if($authMethodsArray == null) throw new NotSupportedException('You must choose at least one auth methods, configure your app\config\params.php for more options.'); 55 | return $authMethodsArray; 56 | } 57 | } -------------------------------------------------------------------------------- /helpers/BehaviorsFromParamsHelper.php: -------------------------------------------------------------------------------- 1 | getBehaviors($behaviors); 45 | } 46 | 47 | private function getBehaviors($behaviors){ 48 | $behaviors['authenticator'] = [ 49 | 'class' => CompositeAuth::class, 50 | 'authMethods' => AuthMethodsFromParamsHelper::authMethods(), 51 | ]; 52 | if(Yii::$app->params['useRateLimiter']){ 53 | $behaviors['rateLimiter']['enableRateLimitHeaders'] = false; 54 | } 55 | return $behaviors; 56 | } 57 | } -------------------------------------------------------------------------------- /migrations/m141022_115823_create_user_table.php: -------------------------------------------------------------------------------- 1 | db->driverName === 'mysql') { 14 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; 15 | } 16 | 17 | $this->createTable('{{%user}}', [ 18 | 'id' => $this->primaryKey(), 19 | 'username' => $this->string()->notNull()->unique(), 20 | 'email' => $this->string()->notNull()->unique(), 21 | 'password_hash' => $this->string()->notNull(), 22 | 'status' => $this->smallInteger()->notNull(), 23 | 'auth_key' => $this->string(32)->notNull(), 24 | 'password_reset_token' => $this->string()->unique(), 25 | 'account_activation_token' => $this->string()->unique(), 26 | 'created_at' => $this->integer()->notNull(), 27 | 'updated_at' => $this->integer()->notNull(), 28 | ], $tableOptions); 29 | } 30 | 31 | public function down() 32 | { 33 | $this->dropTable('{{%user}}'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /migrations/m180221_085153_create_post_table.php: -------------------------------------------------------------------------------- 1 | createTable('post', [ 22 | 'id' => $this->primaryKey(), 23 | 'title' => $this->string(), 24 | 'body' => $this->text(), 25 | ]); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function safeDown() 32 | { 33 | $this->dropTable('post'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /migrations/m200609_112354_create_access_token_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%access_token}}', [ 16 | 'id' => $this->primaryKey(), 17 | 'user_id' => $this->integer(), 18 | 'consumer' => $this->string(), 19 | 'token' => $this->string(32)->notNull()->unique(), 20 | 'access_given' => $this->json(), 21 | 'used_at' => $this->integer(), 22 | 'expire_at' => $this->integer(), 23 | 'created_at' => $this->integer()->notNull(), 24 | 'updated_at' => $this->integer()->notNull(), 25 | ]); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function safeDown() 32 | { 33 | $this->dropTable('{{%access_token}}'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /models/AccessToken.php: -------------------------------------------------------------------------------- 1 | auth_key = Yii::$app->security->generateRandomString(); 57 | $accessToken = new AccessToken(); 58 | $accessToken->user_id = $user->id; 59 | $accessToken->consumer = $user->consumer ?? $accessToken->defaultConsumer; 60 | $accessToken->access_given = $user->access_given ?? $accessToken->defaultAccessGiven; 61 | $accessToken->token = $user->auth_key; 62 | $accessToken->used_at = strtotime("now"); 63 | $accessToken->expire_at = $accessToken->tokenExpiration + $accessToken->used_at; 64 | $accessToken->save(); 65 | } 66 | 67 | /** 68 | * Make all user token based on any user_id expired 69 | * 70 | * @param int @userId 71 | * @return nothing 72 | */ 73 | public static function makeAllUserTokenExpiredByUserId($userId){ 74 | AccessToken::updateAll(['expire_at' => strtotime("now")], ['user_id' => $userId]); 75 | } 76 | 77 | /** 78 | * Expire any access_token 79 | * 80 | * @return bool 81 | */ 82 | public function expireThisToken(){ 83 | $this->expire_at = strtotime("now"); 84 | return $this->save(); 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function rules() 91 | { 92 | return [ 93 | [['user_id', 'used_at', 'expire_at', 'created_at', 'updated_at'], 'integer'], 94 | [['token'], 'required'], 95 | [['access_given'], 'string'], 96 | [['consumer'], 'string', 'max' => 255], 97 | [['token'], 'string', 'max' => 32], 98 | [['token'], 'unique'], 99 | ]; 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function attributeLabels() 106 | { 107 | return [ 108 | 'id' => 'ID', 109 | 'user_id' => 'User ID', 110 | 'consumer' => 'Consumer', 111 | 'token' => 'Token', 112 | 'access_given' => 'Access Given', 113 | 'used_at' => 'Used At', 114 | 'expire_at' => 'Expire At', 115 | 'created_at' => 'Created At', 116 | 'updated_at' => 'Updated At', 117 | ]; 118 | } 119 | 120 | public function behaviors() 121 | { 122 | return [ 123 | TimestampBehavior::class, 124 | ]; 125 | } 126 | } -------------------------------------------------------------------------------- /models/Post.php: -------------------------------------------------------------------------------- 1 | 'Active', 29 | self::STATUS_INACTIVE => 'Inactive', 30 | self::STATUS_DELETED => 'Deleted' 31 | ]; 32 | 33 | /** 34 | * Returns the validation rules for attributes. 35 | * 36 | * @return array 37 | */ 38 | public function rules() 39 | { 40 | return [ 41 | ['username', 'filter', 'filter' => 'trim'], 42 | ['username', 'required'], 43 | ['username', 'string', 'min' => 2, 'max' => 255], 44 | ['username', 'unique'], 45 | [['consumer', 'access_given'], 'safe'], 46 | ['email', 'filter', 'filter' => 'trim'], 47 | ['email', 'required'], 48 | ['email', 'email'], 49 | ['email', 'string', 'max' => 255], 50 | ['status', 'required'], 51 | ]; 52 | } 53 | 54 | /** 55 | * Returns a list of behaviors that this component should behave as. 56 | * 57 | * @return array 58 | */ 59 | public function behaviors() 60 | { 61 | return [ 62 | TimestampBehavior::className(), 63 | ]; 64 | } 65 | 66 | /** 67 | * Returns the attribute labels. 68 | * 69 | * @return array 70 | */ 71 | public function attributeLabels() 72 | { 73 | return [ 74 | 'id' => Yii::t('app', 'ID'), 75 | 'username' => Yii::t('app', 'Username'), 76 | 'password' => Yii::t('app', 'Password'), 77 | 'email' => Yii::t('app', 'Email'), 78 | 'status' => Yii::t('app', 'Status'), 79 | 'created_at' => Yii::t('app', 'Created At'), 80 | 'updated_at' => Yii::t('app', 'Updated At'), 81 | ]; 82 | } 83 | 84 | //------------------------------------------------------------------------------------------------// 85 | // USER FINDERS 86 | //------------------------------------------------------------------------------------------------// 87 | 88 | /** 89 | * Finds user by username. 90 | * 91 | * @param string $username 92 | * @return static|null 93 | */ 94 | public static function findByUsername($username) 95 | { 96 | return static::findOne(['username' => $username]); 97 | } 98 | 99 | /** 100 | * Finds user by email. 101 | * 102 | * @param string $email 103 | * @return static|null 104 | */ 105 | public static function findByEmail($email) 106 | { 107 | return static::findOne(['email' => $email]); 108 | } 109 | 110 | /** 111 | * Finds user by password reset token. 112 | * 113 | * @param string $token Password reset token. 114 | * @return null|static 115 | */ 116 | public static function findByPasswordResetToken($token) 117 | { 118 | if (!static::isPasswordResetTokenValid($token)) { 119 | return null; 120 | } 121 | 122 | return static::findOne([ 123 | 'password_reset_token' => $token, 124 | 'status' => User::STATUS_ACTIVE, 125 | ]); 126 | } 127 | 128 | /** 129 | * Finds user by account activation token. 130 | * 131 | * @param string $token Account activation token. 132 | * @return static|null 133 | */ 134 | public static function findByAccountActivationToken($token) 135 | { 136 | return static::findOne([ 137 | 'account_activation_token' => $token, 138 | 'status' => User::STATUS_INACTIVE, 139 | ]); 140 | } 141 | 142 | 143 | //------------------------------------------------------------------------------------------------// 144 | // HELPERS 145 | //------------------------------------------------------------------------------------------------// 146 | 147 | /** 148 | * Returns the user status in nice format. 149 | * 150 | * @param integer $status Status integer value. 151 | * @return string Nicely formatted status. 152 | */ 153 | public function getStatusName($status) 154 | { 155 | return $this->statusList[$status]; 156 | } 157 | 158 | /** 159 | * Generates new password reset token. 160 | */ 161 | public function generatePasswordResetToken() 162 | { 163 | $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); 164 | } 165 | 166 | /** 167 | * Removes password reset token. 168 | */ 169 | public function removePasswordResetToken() 170 | { 171 | $this->password_reset_token = null; 172 | } 173 | 174 | /** 175 | * Finds out if password reset token is valid. 176 | * 177 | * @param string $token Password reset token. 178 | * @return bool 179 | */ 180 | public static function isPasswordResetTokenValid($token) 181 | { 182 | if (empty($token)) { 183 | return false; 184 | } 185 | 186 | $timestamp = (int) substr($token, strrpos($token, '_') + 1); 187 | $expire = Yii::$app->params['user.passwordResetTokenExpire']; 188 | return $timestamp + $expire >= time(); 189 | } 190 | 191 | /** 192 | * Generates new account activation token. 193 | */ 194 | public function generateAccountActivationToken() 195 | { 196 | $this->account_activation_token = Yii::$app->security->generateRandomString() . '_' . time(); 197 | } 198 | 199 | /** 200 | * Removes account activation token. 201 | */ 202 | public function removeAccountActivationToken() 203 | { 204 | $this->account_activation_token = null; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /models/UserIdentity.php: -------------------------------------------------------------------------------- 1 | $id, 'status' => User::STATUS_ACTIVE]); 54 | } 55 | 56 | /** 57 | * Finds an identity by the given access token. 58 | * 59 | * @param mixed $token 60 | * @param null $type 61 | * @return void|IdentityInterface 62 | * 63 | * @throws NotSupportedException 64 | */ 65 | public static function findIdentityByAccessToken($token, $type = null) 66 | { 67 | $accessToken = AccessToken::find()->where(['token' => $token])->andWhere(['>', 'expire_at', strtotime('now')])->one(); 68 | if(!$accessToken) return $accessToken; 69 | return User::findOne(['id' => $accessToken->user_id]); 70 | // return User::findOne(['auth_key' => $token, 'status' => User::STATUS_ACTIVE]); 71 | } 72 | 73 | /** 74 | * Returns an ID that can uniquely identify a user identity. 75 | * 76 | * @return int|mixed|string 77 | */ 78 | public function getId() 79 | { 80 | return $this->getPrimaryKey(); 81 | } 82 | 83 | /** 84 | * Returns a key that can be used to check the validity of a given 85 | * identity ID. The key should be unique for each individual user, and 86 | * should be persistent so that it can be used to check the validity of 87 | * the user identity. The space of such keys should be big enough to defeat 88 | * potential identity attacks. 89 | * 90 | * @return string 91 | */ 92 | public function getAuthKey() 93 | { 94 | return $this->auth_key; 95 | } 96 | 97 | /** 98 | * Validates the given auth key. 99 | * 100 | * @param string $authKey The given auth key. 101 | * @return boolean Whether the given auth key is valid. 102 | */ 103 | public function validateAuthKey($authKey) 104 | { 105 | return $this->getAuthKey() === $authKey; 106 | } 107 | 108 | //------------------------------------------------------------------------------------------------// 109 | // IMPORTANT IDENTITY HELPERS 110 | //------------------------------------------------------------------------------------------------// 111 | 112 | /** 113 | * Generates "remember me" authentication key. 114 | */ 115 | public function generateAuthKey() 116 | { 117 | $this->auth_key = Yii::$app->security->generateRandomString(); 118 | AccessToken::generateAuthKey($this); 119 | } 120 | 121 | /** 122 | * Validates password. 123 | * 124 | * @param string $password 125 | * @return bool 126 | * 127 | * @throws \yii\base\InvalidConfigException 128 | */ 129 | public function validatePassword($password) 130 | { 131 | return Yii::$app->security->validatePassword($password, $this->password_hash); 132 | } 133 | 134 | /** 135 | * Generates password hash from password and sets it to the model. 136 | * 137 | * @param string $password 138 | * 139 | * @throws \yii\base\Exception 140 | * @throws \yii\base\InvalidConfigException 141 | */ 142 | public function setPassword($password) 143 | { 144 | $this->password_hash = Yii::$app->security->generatePasswordHash($password); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /modules/v1/controllers/DefaultController.php: -------------------------------------------------------------------------------- 1 | Status::STATUS_OK, 19 | 'message' => "You may customize this page by editing the following file:". __FILE__, 20 | 'data' => '' 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /modules/v1/controllers/PostController.php: -------------------------------------------------------------------------------- 1 | run(); -------------------------------------------------------------------------------- /yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 21 | exit($exitCode); 22 | -------------------------------------------------------------------------------- /yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright (c) 2008 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | --------------------------------------------------------------------------------