Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Yii2 Laravel Package

yiisoft/yii2

Yii 2 is a fast, secure, and high-performance PHP framework for building modern web applications. It offers powerful caching, MVC architecture, robust security, and a rich ecosystem of extensions and tools for rapid development.

View on GitHub
Deep Wiki
Context7

Getting Started

Yii2 is a full-stack framework, not a drop-in library — "installing the package" means bootstrapping an application skeleton around it. The core repo (yiisoft/yii2) is a read-only subsplit of the framework; Gii, Debug, Bootstrap, and mailer are separate packages pulled in by the app template.

Minimal steps

composer create-project --prefer-dist yiisoft/yii2-app-basic myapp
cd myapp
php yii serve                      # http://localhost:8080

For API-first work use the advanced template instead (yiisoft/yii2-app-advanced), which gives you frontend/, backend/, common/, and a shared environments/ overlay for per-target config.

Where to look first (in this order)

Path What it is Laravel equivalent
web/index.php Entry point; sets YII_DEBUG, YII_ENV, loads config public/index.php + bootstrap/app.php
config/web.php The whole app: components, modules, URL rules, DI config/*.php + AppServiceProvider
config/db.php PDO DSN/credentials .env + config/database.php
yii (root script) Console entry point, uses config/console.php artisan
controllers/SiteController.php Reference controller app/Http/Controllers
models/, views/ ActiveRecord + PHP/Twig templates Eloquent + Blade

First use case: ship one CRUD slice end-to-end

  1. Enable Gii (dev only) in config/web.php:
    if (YII_ENV_DEV) {
        $config['bootstrap'][] = 'gii';
        $config['modules']['gii'] = ['class' => \yii\gii\Module::class];
    }
    
  2. Create the table via a migration: php yii migrate/create create_post → edit migrations/ → php yii migrate.
  3. Visit /gii, generate a Model from the table, then a CRUD from that model.
  4. Read the generated PostController and views/post/_form.php. That generated code is the idiomatic pattern for the rest of the codebase — copy its style rather than importing Laravel habits.

The single fastest way to get productive is to generate two or three CRUDs with Gii and read every line it emits: access control via behaviors(), Yii::$app->request->post() binding, ActiveForm, GridView, and flash messages all show up there.


Implementation Patterns

Controllers and actions

public function behaviors()
{
    return [
        'access' => [
            'class' => \yii\filters\AccessControl::class,
            'rules' => [
                ['allow' => true, 'actions' => ['index', 'view'], 'roles' => ['?']],
                ['allow' => true, 'roles' => ['@']],
            ],
        ],
        'verbs' => [
            'class' => \yii\filters\VerbFilter::class,
            'actions' => ['delete' => ['POST']],
        ],
    ];
}

Behaviors are Yii's middleware/trait hybrid — they attach to the component and hook its events (beforeAction, afterAction). Prefer them over rewriting base controller classes; they're composable per-controller and are how auth, caching, and rate limiting are normally wired.

ActiveRecord

// Relations drive everything: with() for eager loading, indexBy for keyed results
$posts = Post::find()
    ->with(['author', 'comments'])
    ->where(['status' => Post::STATUS_PUBLISHED])
    ->orderBy(['created_at' => SORT_DESC])
    ->indexBy('id')
    ->limit(20)
    ->all();

// Bulk ops DO NOT run validation, behaviors, or events
Post::updateAll(['status' => Post::STATUS_ARCHIVED], ['<', 'created_at', $cutoff]);

Drop in TimestampBehavior and BlameableBehavior on nearly every model — they replace the created_at/updated_at/created_by boilerplate you'd otherwise write per table:

public function behaviors()
{
    return [
        \yii\behaviors\TimestampBehavior::class,
        [
            'class' => \yii\behaviors\BlameableBehavior::class,
            'attributes' => [
                ActiveRecord::EVENT_BEFORE_INSERT => ['created_by'],
                ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_by'],
            ],
        ],
    ];
}

Forms: separate the write model from the table

The biggest structural shift from Laravel form requests is that Yii expects a yii\base\Model subclass for anything with non-1:1 input (multi-step wizards, search filters, signup + profile in one submit):

class SignupForm extends Model
{
    public $username, $email, $password;

    public function rules()
    {
        return [
            [['username', 'email', 'password'], 'required'],
            ['email', 'email'],
            ['username', 'unique', 'targetClass' => User::class, 'targetAttribute' => 'username'],
            ['password', 'string', 'min' => 8],
        ];
    }

    public function signup()
    {
        if (!$this->validate()) {
            return null;
        }
        $user = new User(['username' => $this->username, 'email' => $this->email]);
        $user->setPassword($this->password);
        return $user->save() ? $user : null;
    }
}

Search/filter forms extend yii\base\Model and are fed to ActiveDataProvider, which is what powers GridView and pagination — this is the pattern in every Gii-generated search() model.

Data providers and grids

$dataProvider = new ActiveDataProvider([
    'query' => Post::find()->with('author'),
    'pagination' => ['pageSize' => 20],
    'sort' => ['defaultOrder' => ['created_at' => SORT_DESC]],
]);
return $this->render('index', ['dataProvider' => $dataProvider, 'searchModel' => $searchModel]);

REST APIs: extend yii\rest\ActiveController, set $modelClass, and you get index/view/create/update/delete with content negotiation, pagination headers, and HTTP caching for free.

class PostController extends \yii\rest\ActiveController
{
    public $modelClass = 'app\models\Post';
    public $serializer = [
        'class' => \yii\rest\Serializer::class,
        'collectionEnvelope' => 'items',
    ];
}

Wire it in config/web.php via urlManager rules with 'parseRequest' => false on the controller and 'pluralize' => false on the rule if you want explicit paths. Add yii\filters\auth\HttpBearerAuth as a behavior for token auth.

Migrations and console work

php yii migrate/create add_index_to_post
php yii migrate --interactive=0      # CI / deploy scripts
php yii migrate/down 1

Anything scheduled, batch, or long-running belongs in a console controller under commands/ and gets called as php yii report/daily. In cron, always pass --interactive=0 and set YII_ENV=prod. For real background work, pull yiisoft/yii2-queue (DB or Redis driver) rather than shelling out to exec().

Config, DI, and environments

// config/web.php — components are lazily built and act as singletons
'components' => [
    'cache' => ['class' => \yii\caching\ApcCache::class],
    'db' => require __DIR__ . '/db.php',
    'mailer' => ['class' => \yii\symfonymailer\Mailer::class, 'useFileTransport' => YII_ENV_DEV],
],

Bind interfaces in the bootstrap section, not in bootstrap files:

Yii::$container->set(\app\contracts\PaymentGateway::class, \app\services\StripeGateway::class);

Use config/web-local.php (basic template) or environments/ (advanced) for per-machine overrides, and commit only the non-local files. Wrap environment conditionals around YII_ENV_DEV rather than hardcoding hostnames.


Gotchas and Tips

The framework is in bugfix-only maintenance. Releases continue (recent tags address PHP 8.3/8.4 compatibility and security patches), but do not architect around new first-party features arriving. Pin yiisoft/yii2 to a caret constraint, run composer outdated deliberately, and treat major extension abandonment as a planning risk — yii2-swiftmailer is already superseded by yii2-symfonymailer. Verify the exact PHP range in the tag's composer.json before upgrading your runtime; the framework's own support matrix lags new PHP releases by a minor version or so.

Yii::$app is a service locator, not a container-first design. You will see Yii::$app->db, Yii::$app->user, Yii::$app->request everywhere. Do not fight it by injecting everything; instead isolate it behind your own service classes if you want testability. Yii::$container exists and works, but it only auto-resolves constructor dependencies — it does not rewrite the framework's internal Yii::$app lookups.

PRG and validation. $model->save() returns false on validation failure with errors on the model; $model->load(Yii::$app->request->post()) only populates safe attributes (those with a rules() entry). If a field silently refuses to save, it's almost always a missing rule — not a DB issue.

Routing 404s on a fresh install are almost always the web server. urlManager with 'enablePrettyUrl' => true, 'showScriptName' => false needs an nginx try_files $uri $uri/ /index.php?$args; or the Apache .htaccess that ships with the template. php yii serve works out of the box, which makes this a deploy-time trap.

Gii and Debug must be env-gated. Both are registered on bootstrap in the stock config; if you copy config/web.php to production without the YII_ENV_DEV guard you have shipped a code generator to the internet. Add an IP allowlist to the Debug module as well — it captures request bodies and SQL.

CSRF breaks JSON APIs. yii\web\Controller::$enableCsrfValidation is on by default; REST clients without a token get 400s. Disable it only on the API controller (or switch to cookie-based CSRF with X-CSRF-Token and HttpBearerAuth). Read payloads with Yii::$app->request->getBodyParams(), not ->post(), so JSON and form-encoded both work.

N+1 is invisible and expensive. $post->author->name inside a GridView loop fires a query per row. Always ->with(['author']) on the ActiveDataProvider query, and add ->asArray() on read-only listings for a measurable memory win.

updateAll(), deleteAll(), and createCommand() bypass the ORM layer entirely — no validation, no behaviors, no events, no afterSave. Fine for backfills; a bug if you rely on TimestampBehavior.

Migrations are not reversible by default. safeUp()/safeDown() are the ones that behave transactionally on MySQL. Use them for anything touching more than one table, and never edit a migration that has already run in shared environments — write a new one.

Config merging is array-based and shallow-ish. Later entries in required config files overwrite whole keys. Debug print_r of the merged result (var_dump(Yii::$app->params)) rather than assuming deep merge semantics, especially when a module overrides a component.

Debugging toolkit. Yii::debug($msg, $category) writes to the debug panel, Yii::error() to runtime/logs/app.log via yii\log\FileTarget. Set 'levels' and 'categories' on the log target or the file grows unbounded — a classic post-launch surprise. Enable yii\debug\Module in dev and read the "DB" and "Timing" panels; they replace most ad-hoc dd() habits.

Testing is Codeception-first. The app templates ship with Codeception suites (tests/unit, tests/functional) that bootstrap the Yii application for you. Pure PHPUnit requires you to require the Yii autoloader and construct an application instance manually — doable but you'll be reimplementing the fixture layer that yii2-codeception / yii2-faker already provide. Fixtures via yii\test\ActiveFixture with load()/unload() are how DB tests stay isolated.

Extension ecosystem conventions. Community packages are named vendor/yii2-* and are configured as components (e.g. 'gridview' => ['class' => 'kartik\grid\Module']). Before writing UI, check kartik-v/yii2-grid, yii2-widget-select2, yii2-date-range, and yii2-queue — they're the de-facto standards and save days. Conversely, check the last-commit date; a large share of Yii2 extensions are unmaintained and will pin you to older dependency trees via composer.json conflicts.

Asset bundles and the @web alias. Asset publishing copies to web/assets/<hash> via assetManager. In containers or read-only filesystems, set 'assetManager' => ['basePath' => '@webroot/assets', 'baseUrl' => '@web/assets', 'linkAssets' => true] and make the directory writable — otherwise styles silently 404 in production while working locally.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource