21 августа 2024 г.
Установка Yii2
Начнём с установки нового Yii2-проекта с помощью Composer (всё делается в терминале IDE/командной строке):
composer create-project --prefer-dist yiisoft/yii2-app-basic yii2-rest-api
cd yii2-rest-api
Настройка базы данных
В config/db.php:
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=yii2_rest_api',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
Создадим базу данных yii2_rest_api.
Миграции
Создадим миграции:
# Создаём миграцию для пользователей
php yii migrate/create create_user_table
# Создаём миграцию для статей
php yii migrate/create create_article_table
# Создаём миграцию для комментариев
php yii migrate/create create_comment_table
Отредактируем миграции
Миграция для пользователей (migrations/mXXXXXX_XXXXXX_create_user_table.php):
public function safeUp()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%user}}', [
'id' => $this->primaryKey(),
'username' => $this->string(255)->notNull()->unique(),
'email' => $this->string(255)->notNull()->unique(),
'password_hash' => $this->string(255)->notNull(),
'auth_key' => $this->string(32)->notNull(),
'access_token' => $this->string(255)->unique(),
'created_at' => $this->timestamp()->notNull()->defaultExpression('CURRENT_TIMESTAMP'),
'updated_at' => $this->timestamp()->notNull()->defaultExpression('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
], $tableOptions);
}
public function safeDown()
{
$this->dropTable('{{%user}}');
}
Миграция для статей (migrations/mXXXXXX_XXXXXX_create_article_table.php):
public function safeUp()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%article}}', [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
'title' => $this->string(255)->notNull(),
'content' => $this->text()->notNull(),
'created_at' => $this->timestamp()->notNull()->defaultExpression('CURRENT_TIMESTAMP'),
'updated_at' => $this->timestamp()->notNull()->defaultExpression('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
], $tableOptions);
// Внешний ключ и индексы
$this->addForeignKey(
'fk-article-user_id',
'{{%article}}',
'user_id',
'{{%user}}',
'id',
'CASCADE',
'CASCADE'
);
$this->createIndex('idx-article-user_id', '{{%article}}', 'user_id');
}
public function safeDown()
{
$this->dropForeignKey('fk-article-user_id', '{{%article}}');
$this->dropTable('{{%article}}');
}
Миграция для комментариев (migrations/mXXXXXX_XXXXXX_create_comment_table.php):
public function safeUp()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
}
$this->createTable('{{%comment}}', [
'id' => $this->primaryKey(),
'article_id' => $this->integer()->notNull(),
'user_id' => $this->integer()->notNull(),
'content' => $this->text()->notNull(),
'created_at' => $this->timestamp()->notNull()->defaultExpression('CURRENT_TIMESTAMP'),
'updated_at' => $this->timestamp()->notNull()->defaultExpression('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
], $tableOptions);
// Внешние ключи
$this->addForeignKey(
'fk-comment-article_id',
'{{%comment}}',
'article_id',
'{{%article}}',
'id',
'CASCADE',
'CASCADE'
);
$this->addForeignKey(
'fk-comment-user_id',
'{{%comment}}',
'user_id',
'{{%user}}',
'id',
'CASCADE',
'CASCADE'
);
// Индексы
$this->createIndex('idx-comment-article_id', '{{%comment}}', 'article_id');
$this->createIndex('idx-comment-user_id', '{{%comment}}', 'user_id');
}
public function safeDown()
{
$this->dropForeignKey('fk-comment-article_id', '{{%comment}}');
$this->dropForeignKey('fk-comment-user_id', '{{%comment}}');
$this->dropTable('{{%comment}}');
}
Применение миграций:
php yii migrate/up
Другие полезные команды для миграций:
- php yii migrate/create - Создаёт новую миграцию.
- php yii migrate/down - Откатывает приложение, возвращая старые миграции.
- php yii migrate/fresh - Удаляет все таблицы и их связи. Все миграции сначала.
- php yii migrate/history - Показывает историю миграций.
- php yii migrate/mark - Изменяет историю миграций на указанную версию.
- php yii migrate/new - Показывает новые миграции, которые ещё не применены.
- php yii migrate/redo - Повторяет последние миграции.
- php yii migrate/to - Обновляет или откатывает до указанной версии.
- php yii migrate/up (по умолчанию) - Обновляет приложение, применяя новые миграции.
Создание моделей
Создадим модели:
# Создаём модель для пользователей (если отсутствует)
php yii gii/model --tableName=user --modelClass=User
# Создаём модель для статей
php yii gii/model --tableName=article --modelClass=Article
# Создаём модель для комментариев
php yii gii/model --tableName=comment --modelClass=Comment
Или создадим через веб-интерфейс:
php yii serve --port=8888
http://localhost:8888/index.php?r=gii
#или
http://localhost:8888/gii
Редактирование сгенерированных моделей
Модель User (models/User.php):
<?php
namespace app\models;
use Yii;
use yii\web\IdentityInterface;
class User extends \yii\db\ActiveRecord implements IdentityInterface
{
public static function tableName()
{
return 'user';
}
public function rules()
{
return [
[['username', 'email', 'password_hash', 'auth_key'], 'required'],
[['username', 'email'], 'unique'],
[['email'], 'email'],
[['created_at', 'updated_at'], 'safe'],
[['username', 'email', 'password_hash', 'access_token'], 'string', 'max' => 255],
[['auth_key'], 'string', 'max' => 32],
];
}
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->auth_key;
}
public function validateAuthKey($authKey)
{
return $this->auth_key === $authKey;
}
public function validatePassword($password)
{
return Yii::$app->security->validatePassword($password, $this->password_hash);
}
public function setPassword($password)
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
public function generateAccessToken()
{
$this->access_token = Yii::$app->security->generateRandomString();
}
public function generateAuthKey()
{
$this->auth_key = Yii::$app->security->generateRandomString();
}
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if ($this->isNewRecord) {
$this->generateAuthKey();
}
return true;
}
return false;
}
// Relations
public function getArticles()
{
return $this->hasMany(Article::class, ['user_id' => 'id']);
}
public function getComments()
{
return $this->hasMany(Comment::class, ['user_id' => 'id']);
}
}
Модель Article (models/Article.php):
<?php
namespace app\models;
use Yii;
use yii\db\ActiveRecord;
class Article extends ActiveRecord
{
public static function tableName()
{
return 'article';
}
public function rules()
{
return [
[['user_id', 'title', 'content'], 'required'],
[['user_id'], 'integer'],
[['content'], 'string'],
[['created_at', 'updated_at'], 'safe'],
[['title'], 'string', 'max' => 255],
[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['user_id' => 'id']],
];
}
public function attributeLabels()
{
return [
'id' => 'ID',
'user_id' => 'User ID',
'title' => 'Title',
'content' => 'Content',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
];
}
// Relations
public function getUser()
{
return $this->hasOne(User::class, ['id' => 'user_id']);
}
public function getComments()
{
return $this->hasMany(Comment::class, ['article_id' => 'id']);
}
}
Модель Comment (models/Comment.php):
<?php
namespace app\models;
class Comment extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'comment';
}
public function rules()
{
return [
[['article_id', 'user_id', 'content'], 'required'],
[['article_id', 'user_id'], 'integer'],
[['content'], 'string'],
[['created_at', 'updated_at'], 'safe'],
[['article_id'], 'exist', 'skipOnError' => true, 'targetClass' => Article::class, 'targetAttribute' => ['article_id' => 'id']],
[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['user_id' => 'id']],
];
}
public function getArticle()
{
return $this->hasOne(Article::class, ['id' => 'article_id']);
}
public function getUser()
{
return $this->hasOne(User::class, ['id' => 'user_id']);
}
}
Создание контроллеров
Создадим контроллеры:
# Создаём контроллер для пользователей (если отсутствует)
php yii gii/controller --controllerClass=app\controllers\UserController
# Создаём контроллер для статей
php yii gii/controller --controllerClass=app\controllers\ArticleController
# Создаём контроллер для комментариев
php yii gii/controller --controllerClass=app\controllers\CommentController
# Создаём контроллер для авторизации
php yii gii/controller --controllerClass=app\controllers\AuthController
AuthController (controllers/AuthController.php):
<?php
namespace app\controllers;
use Yii;
use yii\rest\Controller;
use app\models\User;
class AuthController extends Controller
{
public function behaviors()
{
$behaviors = parent::behaviors();
// Убираем аутентификацию для регистрации и логина
unset($behaviors['authenticator']);
// Разрешаем CORS
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::className(),
'cors' => [
'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => null,
'Access-Control-Max-Age' => 86400,
'Access-Control-Expose-Headers' => [],
],
];
return $behaviors;
}
public function actionRegister()
{
Yii::info('Register action called', 'auth');
// Получаем raw body и декодим JSON
$rawBody = Yii::$app->request->getRawBody();
$data = json_decode($rawBody, true);
Yii::info('Raw data: ' . print_r($data, true), 'auth');
Yii::info('POST data: ' . print_r(Yii::$app->request->post(), true), 'auth');
if (json_last_error() !== JSON_ERROR_NONE) {
Yii::$app->response->setStatusCode(400);
return [
'success' => false,
'message' => 'Invalid JSON format',
'json_error' => json_last_error_msg()
];
}
if (empty($data['username']) || empty($data['email']) || empty($data['password'])) {
Yii::$app->response->setStatusCode(400);
return [
'success' => false,
'message' => 'Missing required fields',
'received_data' => $data, // Добавим для отладки
'required' => ['username', 'email', 'password']
];
}
// Проверяем, существует ли пользователь
if (User::find()->where(['username' => $data['username']])->exists()) {
Yii::$app->response->setStatusCode(409);
return [
'success' => false,
'message' => 'Username already exists'
];
}
if (User::find()->where(['email' => $data['email']])->exists()) {
Yii::$app->response->setStatusCode(409);
return [
'success' => false,
'message' => 'Email already exists'
];
}
$user = new User();
$user->username = $data['username'];
$user->email = $data['email'];
$user->setPassword($data['password']);
$user->generateAuthKey();
$user->generateAccessToken();
if ($user->save()) {
Yii::$app->response->setStatusCode(201);
return [
'success' => true,
'message' => 'User registered successfully',
'user' => [
'id' => $user->id,
'username' => $user->username,
'email' => $user->email,
'access_token' => $user->access_token
]
];
} else {
Yii::$app->response->setStatusCode(422);
return [
'success' => false,
'message' => 'Validation failed',
'errors' => $user->getErrors()
];
}
}
public function actionLogin()
{
$rawBody = Yii::$app->request->getRawBody();
$data = json_decode($rawBody, true);
if (empty($data['username']) || empty($data['password'])) {
Yii::$app->response->setStatusCode(400);
return [
'success' => false,
'message' => 'Username and password are required'
];
}
$user = User::findOne(['username' => $data['username']]);
if ($user && $user->validatePassword($data['password'])) {
// Генерируем новый токен при каждом входе
$user->generateAccessToken();
if ($user->save()) {
return [
'success' => true,
'message' => 'Login successful',
'data' => [
'access_token' => $user->access_token,
'user_id' => $user->id,
'username' => $user->username
]
];
}
}
Yii::$app->response->setStatusCode(401);
return [
'success' => false,
'message' => 'Invalid username or password'
];
}
}
ArticleController (controllers/ArticleController.php):
<?php
namespace app\controllers;
use Yii;
use yii\rest\Controller;
use yii\filters\auth\HttpBearerAuth;
use yii\web\ForbiddenHttpException;
use yii\web\NotFoundHttpException;
use app\models\Article;
class ArticleController extends Controller
{
public function behaviors()
{
$behaviors = parent::behaviors();
// Добавляем CORS
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::className(),
'cors' => [
'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => null,
'Access-Control-Max-Age' => 86400,
],
];
// Добавляем Bearer аутентификацию
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['index', 'view', 'options'], // Разрешаем просмотр без аутентификации
];
return $behaviors;
}
/**
* GET /articles - Получить список всех статей
*/
public function actionIndex()
{
$query = Article::find()->with('user');
// Пагинация
$page = Yii::$app->request->get('page', 1);
$perPage = Yii::$app->request->get('per-page', 20);
$dataProvider = new \yii\data\ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => $perPage,
'page' => $page - 1, // Yii использует 0-based пагинацию
],
'sort' => [
'defaultOrder' => [
'created_at' => SORT_DESC,
]
]
]);
return $dataProvider;
}
/**
* GET /articles/{id} - Получить конкретную статью
*/
public function actionView($id)
{
$article = Article::find()
->where(['id' => $id])
->with('user', 'comments')
->one();
if (!$article) {
throw new NotFoundHttpException("Article not found");
}
return $article;
}
/**
* POST /articles - Создать новую статью
*/
public function actionCreate()
{
$model = new Article();
$data = Yii::$app->request->post();
$model->load($data, '');
$model->user_id = Yii::$app->user->id; // Устанавливаем user_id из токена
if ($model->save()) {
Yii::$app->response->setStatusCode(201);
return $model;
} else {
Yii::$app->response->setStatusCode(422);
return [
'errors' => $model->getErrors(),
'message' => 'Validation failed'
];
}
}
/**
* PUT /articles/{id} - Обновить статью
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
$this->checkArticleOwnership($model);
$data = Yii::$app->request->post();
$model->load($data, '');
if ($model->save()) {
return $model;
} else {
Yii::$app->response->setStatusCode(422);
return [
'errors' => $model->getErrors(),
'message' => 'Validation failed'
];
}
}
/**
* DELETE /articles/{id} - Удалить статью
*/
public function actionDelete($id)
{
$model = $this->findModel($id);
$this->checkArticleOwnership($model);
if ($model->delete()) {
Yii::$app->response->setStatusCode(204);
return null;
} else {
Yii::$app->response->setStatusCode(500);
return [
'message' => 'Failed to delete article'
];
}
}
/**
* OPTIONS /articles - CORS preflight
*/
public function actionOptions()
{
Yii::$app->response->setStatusCode(200);
return null;
}
/**
* Найти модель статьи по ID
*/
protected function findModel($id)
{
$model = Article::findOne($id);
if (!$model) {
throw new NotFoundHttpException("Article not found");
}
return $model;
}
/**
* Проверить, что пользователь является владельцем статьи
*/
protected function checkArticleOwnership($model)
{
if ($model->user_id !== Yii::$app->user->id) {
throw new ForbiddenHttpException('You can only manage your own articles.');
}
}
}
CommentController (controllers/CommentController.php):
<?php
namespace app\controllers;
use Yii;
use yii\rest\Controller;
use yii\filters\auth\HttpBearerAuth;
use yii\web\ForbiddenHttpException;
use yii\web\NotFoundHttpException;
use app\models\Comment;
class CommentController extends Controller
{
public function behaviors()
{
$behaviors = parent::behaviors();
// Добавляем CORS
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::className(),
'cors' => [
'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => null,
'Access-Control-Max-Age' => 86400,
],
];
// Добавляем Bearer аутентификацию
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['index', 'view', 'options'], // Разрешаем просмотр без аутентификации
];
return $behaviors;
}
/**
* GET /comments - Получить список всех комментариев
*/
public function actionIndex()
{
$query = Comment::find()->with(['user', 'article']);
// Фильтрация по article_id
$articleId = Yii::$app->request->get('article_id');
if ($articleId) {
$query->andWhere(['article_id' => $articleId]);
}
// Фильтрация по user_id
$userId = Yii::$app->request->get('user_id');
if ($userId) {
$query->andWhere(['user_id' => $userId]);
}
// Пагинация
$page = Yii::$app->request->get('page', 1);
$perPage = Yii::$app->request->get('per-page', 20);
// Сортировка
$sort = Yii::$app->request->get('sort', '-created_at');
if ($sort === '-created_at') {
$query->orderBy(['created_at' => SORT_DESC]);
} elseif ($sort === 'created_at') {
$query->orderBy(['created_at' => SORT_ASC]);
}
$dataProvider = new \yii\data\ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => $perPage,
'page' => $page - 1,
]
]);
return $dataProvider;
}
/**
* GET /comments/{id} - Получить конкретный комментарий
*/
public function actionView($id)
{
$comment = Comment::find()
->where(['id' => $id])
->with(['user', 'article'])
->one();
if (!$comment) {
throw new NotFoundHttpException("Comment not found");
}
return $comment;
}
/**
* POST /comments - Создать новый комментарий
*/
public function actionCreate()
{
$model = new Comment();
$data = Yii::$app->request->post();
$model->load($data, '');
$model->user_id = Yii::$app->user->id; // Устанавливаем user_id из токена
if ($model->save()) {
Yii::$app->response->setStatusCode(201);
// Возвращаем комментарий с связанными данными
return Comment::find()
->where(['id' => $model->id])
->with(['user', 'article'])
->one();
} else {
Yii::$app->response->setStatusCode(422);
return [
'errors' => $model->getErrors(),
'message' => 'Validation failed'
];
}
}
/**
* PUT /comments/{id} - Обновить комментарий
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
$this->checkCommentOwnership($model);
$data = Yii::$app->request->post();
$model->load($data, '');
if ($model->save()) {
// Возвращаем обновлённый комментарий с связанными данными
return Comment::find()
->where(['id' => $model->id])
->with(['user', 'article'])
->one();
} else {
Yii::$app->response->setStatusCode(422);
return [
'errors' => $model->getErrors(),
'message' => 'Validation failed'
];
}
}
/**
* DELETE /comments/{id} - Удалить комментарий
*/
public function actionDelete($id)
{
$model = $this->findModel($id);
$this->checkCommentOwnership($model);
if ($model->delete()) {
Yii::$app->response->setStatusCode(204);
return null;
} else {
Yii::$app->response->setStatusCode(500);
return [
'message' => 'Failed to delete comment'
];
}
}
/**
* OPTIONS /comments - CORS preflight
*/
public function actionOptions()
{
Yii::$app->response->setStatusCode(200);
return null;
}
/**
* Найти модель комментария по ID
*/
protected function findModel($id)
{
$model = Comment::findOne($id);
if (!$model) {
throw new NotFoundHttpException("Comment not found");
}
return $model;
}
/**
* Проверить, что пользователь является владельцем комментария
*/
protected function checkCommentOwnership($model)
{
if ($model->user_id !== Yii::$app->user->id) {
throw new ForbiddenHttpException('You can only manage your own comments.');
}
}
}
Настройка REST API в конфигурации
В config/web.php:
...
'components' => [
'request' => [
'cookieValidationKey' => 'xxxxxxxxxxxxxxxxxxxxx',
'parsers' => [
'application/json' => 'yii\web\JsonParser',
],
],
...
И здесь config/web.php:
...
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['user', 'article', 'comment'],
],
'POST auth/login' => 'auth/login',
'POST auth/register' => 'auth/register',
],
],
...
Тестирование
Регистрация:
curl -v -X POST http://localhost:8888/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"testuser","email":"test@example.com","password":"password123"}'
Авторизация:
curl -X POST http://localhost:8888/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"password123"}'
Работа со статьями:
# Получить все статьи
curl -X GET http://localhost:8888/articles
# Получить статью по ID
curl -X GET http://localhost:8888/articles/1
# Создать статью
curl -X POST http://localhost:8888/articles \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"title":"My Article","content":"Article content"}'
# Обновить статью
curl -X PUT http://localhost:8888/articles/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"title":"Updated Title","content":"Updated content"}'
# Удалить статью
curl -X DELETE http://localhost:8888/articles/1 \
-H "Authorization: Bearer YOUR_TOKEN"
Работа с комментариями:
#Получить все комментарии
curl -X GET http://localhost:8888/comments
#Создать комментарий
curl -X POST http://localhost:8888/comments \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"article_id": 1,
"content": "Отличная статья! Очень полезно."
}'
#Получить комментарии конкретной статьи
curl -X GET "http://localhost:8888/comments?article_id=1"
#Получить комментарии конкретного пользователя
curl -X GET "http://localhost:8888/comments?user_id=1"
#Получить комментарий по ID
curl -X GET http://localhost:8888/comments/1
#Обновить комментарий
curl -X PUT http://localhost:8888/comments/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"content": "Обновлённый текст комментария"
}'
#Удалить комментарий
curl -X DELETE http://localhost:8888/comments/1 \
-H "Authorization: Bearer YOUR_TOKEN"
#Комментарии с сортировкой и пагинацией
curl -X GET "http://localhost:8888/comments?article_id=1&sort=-created_at&page=1&per-page=10"
Все команды Gii
- php yii gii/controller — Генерирует новый контроллер (создаёт файл контроллера с выбранным именем).
- php yii gii/crud — Генерирует полный CRUD (Create, Read, Update, Delete) для указанной модели и таблицы.
- php yii gii/extension — Создаёт расширение Yii (расширяет функциональность проекта).
- php yii gii/form — Генерирует форму для модели (обычно для быстрого создания форм).
- php yii gii/index — Показывает страницу Gii, где можно выбрать генераторы и настроить их (это основной интерфейс Gii).
- php yii gii/model — Генерирует модель на основе таблицы базы данных.
- php yii gii/module — Создаёт новый модуль Yii (отдельный компонент приложения).