Yii2: Moving to Bootstrap 4

Note: this post is technical in nature and explains how to do a specific thing.  Unless you want to use composer to move to Bootstrap 4, or need to upgrade to Bootstrap 4 manually, this article is probably one you can skip.

Thanks to the Ladybug podcast I found a feature in the Chrome / Chromium dev tools called Lighthouse.  This tool examines your site for performance, search engine optimisation, accessibility and best practices.  I've been running this on a number of my sites and Lighthouse highlighted some of my frontend Javascript libraries contained known vulnerabilities:

Lighthouse report showing the issues.

Clearly I needed to fix this.

Acknowledgements

My starting instructions came from the Yii Framework wiki's instructions for upgrading to Bootstrap 4.  My thanks go to Richard Pillay for writing that very useful guide.  I'd also like to thank my long-time coding partner, Adam, for giving me some pointers along the way.  There's also further details in another Yii2 Bootstrap 4 migration guide here.

Recommendations

Before undertaking changes of this magnitude, and there will be a number, I'd recommend switching to a new branch in your version control system.  In git this is as easy as git branch feature/upgradeBootstrap4 (or to make a branch and switch to it immediately: git checkout -b feature/upgradeBootstrap4).

Adding Bootstrap 4 via Composer

In theory this should be a simple case of issuing composer require --prefer-dist yiisoft/yii2-bootstrap4 however this gave me an error:

yiisoft/yii2-bootstrap4 2.0.8 requires npm-asset/bootstrap ^4.3 -> no matching package found

This failed as there was no npm-asset/bootstrap release in my existing repository.  Turns out I needed to point composer at a different repository (instructions from the Yii2 docs).  Edit composer.json and add a repositories section:

"repositories": [
    {
        "type": "composer",
        "url": "https://asset-packagist.org"
    }
]
Adding the repositories section to composer.json.

It's then necessary to set up aliases in your config/web.php file:

$config = [
    ...
    'aliases' => [
        '@bower' => '@vendor/bower-asset',
        '@npm'   => '@vendor/npm-asset',
    ],
    ...
];
Adding aliases to config/web.php.

Run composer update

After changing repository it's worth performing a composer update before we start doing any work to get Bootstrap 4.  In my case the update removed some resources that had already been installed so make a note of what's removed so you can fix that later.

Install Bootstrap 4

Now we've got the new repository added, and all our existing resources are up to date, it's time to install Bootstrap 4.  Run composer require --prefer-dist yiisoft/yii2-bootstrap4 like we tried originally, except this time the installation should be successful.

Update your code to use Bootstrap 4

You're not done after adding Bootstrap 4, and your application won't be using it yet.  It's necessary to update your code to switch to using Bootstrap 4, and we do that by refactoring each instance of yii\bootstrap\ to yii\bootstrap4\.  While it's possibly safe to just do a find and replace all here, I'd recommend looking at each instance before you change it.

Fixes

After telling Yii2 to move to Bootstrap 4 you'll notice some things are broken, or not showing as intended.  Due to changes in the way Bootstrap 4 works, compared to Bootstrap 3, it's necessary to make further changes to our code.

This is probably the first thing you'll notice is wrong, as the navigation bar (navbar) will be completely wrong.  Initially the navbar will be missing entirely.  Open views/layouts/main.php and locate the NavBar::begin([ line.  Beneath that you'll see the options array and an entry for class.  Mine was set to navbar-inverse navbar-fixed-top.  To get our navbar to show up we need to add a background (thanks to this article) so we can change the class to get things going:

'class' => 'navbar-light or navbar-dark navbar-expand',
Initial change so we can at least see the navbar.

Once we've got the navbar back you'll note items are aligned left (next to the brand / logo) whereas they should be aligned right:

The original navbar (top, Bootstrap 3) vs the Bootstrap 4 rendering (bottom).

Examining the CSS showed there were no pull-left or pull-right CSS classes any more, and nothing in the navbar's CSS to say "put the brand / logo on the left and the navigation items on the right".  I switched to my site's CSS (web/css/site.css) and added classes for this.  I also created a new menu container class:

.pull-left {
    float: left !important;
}

.pull-right {
    float: right !important;
}

.menu-container {
    width: 100%;
}
My new CSS classes.

Now I needed to apply changes to my navbar.  For this I placed the navbar in a new div container (echo '<div class="menu-container">'; above echo Nav::widget([).  Then I changed the CSS classes on the menu to include pull-right:

'options' => ['class' => 'navbar-nav navbar-right pull-right'],
Navbar class assignment.

The navbar should now look more like you want it.

Following the upgrade the breadcrumbs along the top of each page will be broken:

The broken breadcrumb (circled).

Thanks to Davide in the comments of the aforementioned page, this is actually a relatively easy fix:

Edit views/layouts/main.php and replace

use yii\widgets\Breadcrumbs;
Original code (pre-Bootstrap 4).

with:

use yii\bootstrap4\Breadcrumbs;
Replacement code (post upgrade).
Following the fix the / is displayed correctly.

Missing ActionColumn icons

This will only be a problem if you're using the GridView widget with the ActionColumn.

For me, this was a bit of a fiddle to get working, and required a reasonable amount of refactoring.  First, install FontAwesome by adding to your composer.json, and install the package with composer as normal.

"rmrevin/yii2-fontawesome": "~3.5"
Add this to composer.json.

Next it's necessary to refactor the buttons on the ActionColumn widgets to define the buttons to use the new font.  This involves defining the button actions by hand, as the template actions will be incorrect.

echo GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel'  => $searchModel,
        'formatter'    => ['class' => 'yii\i18n\Formatter', 'nullDisplay' => ''],
        'columns'      => [
            'title',
            'body:ntext',
            [
                'attribute' => 'published',
                'label'     => 'Published',
                'value'     => 'published',
                'format'    => ['date', 'php:j M Y']
            ],
            [
                'class'          => 'yii\grid\ActionColumn',
                'template'       => '{update}{view}{delete}',
                'buttons'        => [
                    'update' => function ($url, $model) {
                        return Html::a('<i class="fas fa-edit"></i>', $url, [
                            'title' => Yii::t('app', 'Update')
                        ]);
                    },
                    'view'   => function ($url, $model) {
                        return Html::a('<i class="fas fa-eye"></i>', $url, [
                            'title' => Yii::t('app', 'View')
                        ]);
                    },
                    'delete' => function ($url, $model, $key) {
                        return Html::a('<span class="fas fa-trash"></span>', $url, [
                            'title'        => Yii::t('yii', 'Delete'),
                            'data-confirm' => Yii::t('yii',
                                'Are you sure you want to delete this item'"?'),
                            'data-method'  => 'post',
                            'data-pjax'    => '0',
                        ]);
                    }
                ]
            ]
        ],
    ]);
The Bootstrap 4 changes are in the buttons array.

A better solution is to redefine the template code (inserted via 'template'       => '{update}{view}{delete}',).  If I find how to do that I'll update this blog post.  If you know how to do this please ping me a message on Twitter (@joncojonathan), thanks.

Other fixes

There will be other fixes that are needed.  For example, I still had issues with buttons not showing and the navbar height changing on some pages.  These should be fixable by making further changes to your CSS, an exercise I leave for the reader.


Banner image: Screenshot from the Bootstrap homepage.