jayshah88 / yii2-multilevel-category
Create n-level hierarchical category dropdowns for Yii2 from an adjacency-list table using a single query
Package info
github.com/jayshah88/yii2-multilevel-category
Type:yii2-extension
pkg:composer/jayshah88/yii2-multilevel-category
Requires
- php: ^7.4 || ^8.0
- yiisoft/yii2: ^2.0.46
Requires (Dev)
- composer/installers: ^2.3
- oomphinc/composer-installers-extender: ^2.0
- phpunit/phpunit: ^9.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-10-25 04:28:05 UTC
README
yii2-multilevel-category builds n-level hierarchical category dropdowns for
Yii2 from an adjacency-list table (a single column, root by default, holds
the id of the parent row).
Features
- Renders
id => indented labeloption lists ready fordropDownList()at any nesting depth. - Loads the whole tree with a single query (the 1.x version issued one query per node).
- Accepts active record instances,
ActiveQueryobjects, model class names, raw ids,asArray()rows ornull(auto-detects root rows). - Configurable attribute names, root label, indentation and ordering.
- Cycle-safe: corrupt adjacency data can no longer cause infinite recursion.
Multilevel::buildTree()returns a nested tree for custom rendering (<ul>menus,<optgroup>s, etc.).- Backward compatible: the 1.x call signature still works.
Requirements
- PHP 7.4 or newer (7.4 - 8.x).
- Yii2 2.0.46 or newer.
Installation
composer require jayshah88/yii2-multilevel-category
Usage
Classic, 1.x compatible
use Jay\Multilevel; use common\models\base\Category; $category = new Category(); $parents = Category::find()->where(['root' => 0])->all(); echo $form->field($model, 'root') ->dropDownList(Multilevel::makeDropDown($parents, $category));
Recommended
Root rows are detected automatically, $model can be a class name, an active
record instance or an ActiveQuery:
use Jay\Multilevel; use common\models\base\Category; echo $form->field($model, 'root') ->dropDownList(Multilevel::makeDropDown(null, Category::class));
Options
Pass any of the public properties as the third argument:
$items = Multilevel::makeDropDown(null, Category::class, [ 'idAttribute' => 'id', // primary-key attribute 'parentAttribute' => 'root', // attribute holding the parent id 'titleAttribute' => 'title', // attribute rendered as the label 'rootValue' => 0, // value identifying root rows 'rootLabel' => '-- ROOT --', // null to omit the root option 'indent' => '---', // indentation per level 'orderAttribute' => 'title', // order siblings by (null = by id) 'orderDirection' => SORT_ASC, // SORT_ASC or SORT_DESC 'maxDepth' => 5, // safety net against runaway trees 'queryCallback' => function ($query) { // scope the rows loaded for very large tables $query->andWhere(['status' => 1]); }, ]);
The same options can also be set per instance or application-wide via the DI container:
$ml = new Multilevel(['titleAttribute' => 'name']); echo $form->field($model, 'root')->dropDownList($ml->getItems(null, Category::class));
Nested rendering
buildTree() returns the hierarchy as a nested array, ideal for menus:
use Jay\Multilevel; use yii\helpers\Html; $tree = Multilevel::buildTree(null, Category::class, ['orderAttribute' => 'title']); function renderList(array $nodes): void { echo '<ul>'; foreach ($nodes as $node) { echo '<li>' . Html::encode($node['title']); renderList($node['children']); echo '</li>'; } echo '</ul>'; } renderList($tree);
Each branch contains node (the active record or array row), id, title
and children.
How it works
All category rows are loaded with one query and linked in memory by their
parent attribute, so the cost is O(n) database work regardless of the tree
depth. Rows whose parent cannot be resolved are treated as orphans and are
hidden, exactly like in 1.x. Cyclic rows (a parent chain that loops back on
itself) are skipped instead of recursing forever. For very large tables use
queryCallback to limit the loaded rows.
Database schema
See sample-data.sql for a ready-to-use schema: the table needs a primary
key (id), the parent column (root, 0 = top level) and a label column
(title). An index on the parent column is recommended.
Testing
composer install
composer test
The test suite runs against an in-memory SQLite database, so no external services are required.
Upgrading from 1.x
- The class now lives in
src/Multilevel.phpand is autoloaded properly on case-sensitive filesystems (the old layout only worked on Windows). use jay\Multilevel;anduse Jay\Multilevel;are both supported.Multilevel::makeDropDown($parents, $model)keeps working unchanged.subDropDown()was removed (it relied onglobal $data).
See CHANGELOG.md for details.
License
BSD-3-Clause. See LICENSE.