Yii2 Custom Widgets & Reusable Components

Introduction

Custom widgets in Yii2 allow developers to create reusable UI components, improving code modularity and maintainability. Yii2 provides a powerful Widget class that makes it easy to build custom widgets with configurable properties and dynamic rendering capabilities.

  • This tutorial covers:
  • Default use case of Yii2 widgets
  • Creating a simple custom widget
  • Real-world example: Flash message alert widget
  • Create Custom Resuable Components

1. Default Use Case: Creating a Simple Yii2 Widget

Step 1: Create a Custom Widget

In widgets/GreetingWidget.php, create a simple widget that displays a greeting message:

namespace app\widgets;

use yii\base\Widget;
use yii\helpers\Html;

class GreetingWidget extends Widget
{
    public $message;

    public function init()
    {
        parent::init();
        if ($this->message === null) {
            $this->message = 'Hello, Guest!';
        }
    }

    public function run()
    {
        return Html::tag('h3', Html::encode($this->message), ['class' => 'greeting-message']);
    }
}

Step 2: Use the Widget in a View

use app\widgets\GreetingWidget;

// Basic usage
echo GreetingWidget::widget();

// With a custom message
echo GreetingWidget::widget(['message' => 'Welcome to Yii2!']);

Benefits of Widgets:

  • Reusable across multiple views
  • Allows separation of UI logic from controllers
  • Can be extended for dynamic rendering

Real-World Example: Alert Widget

To create a more advanced widget, let's build an Alert Widget that displays flash messages from the session.

Step 1: Create the Alert Widget Class

In widgets/Alert.php, define the widget:

namespace app\widgets;

use Yii;
use yii\base\Widget;

class Alert extends Widget
{
    public $alertTypes = [
        'error'   => 'error',
        'danger'  => 'error',
        'success' => 'success',
        'info'    => 'info',
        'warning' => 'warning'
    ];

    public function run()
    {
        $session = Yii::$app->session;
        $flashes = $session->getAllFlashes();

        foreach ($flashes as $type => $flash) {
            if (!isset($this->alertTypes[$type])) {
                continue;
            }

            foreach ((array) $flash as $message) {
                return "<script>notif({ type: '{$this->alertTypes[$type]}', msg: '{$message}', position: 'right' });</script>";
            }

            $session->removeFlash($type);
        }
    }
}

Step 2: Add the Notification Library

To use the notif() function, include the CDN in your layout file (layouts/main.php):

<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/notifit/1.0.0/notifit.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/notifit/1.0.0/notifit.min.css">
</head>

Step 3: Use the Widget in a Layout File

In layouts/main.php, include the widget:

use app\widgets\Alert;

<?= Alert::widget(); ?>

Now, whenever you set a flash message in the controller:

Yii::$app->session->setFlash('success', 'Your action was successful!');

The widget will automatically display the message in the view using modern alert popups.


2. Creating Custom Components

Components in Yii2 extend core functionalities by providing reusable logic that can be used across the application. Here, we will build a custom log target and a WhatsApp notification system.

Example: WhatsApp Log Target Component

This log target will send log messages to WhatsApp.

Step 1: Create WhatsappLogTarget.php

namespace app\packages\components;

use Yii;
use yii\log\Target;
use yii\log\LogRuntimeException;

class WhatsappLogTarget extends Target
{
    public $except = [
        'yii\web\HttpException:404',
        'yii\web\HttpException:400',
    ];

    public function export()
    {
        $text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n";
        $message_text = '';

        if (isset($_SERVER['SERVER_NAME'])) {
            $message_text .= 'SERVER_NAME : ' . $_SERVER['SERVER_NAME'] . PHP_EOL;
        } else {
            if (isset(Yii::$app->params['baseurl'])) {
                $message_text .= 'Project Name : ' . Yii::$app->params['baseurl'] . PHP_EOL;
            } else {
                $message_text .= 'PWD : ' . $_SERVER['PWD'] . PHP_EOL;
            }
        }

        $message_text .= PHP_EOL . 'Error Summary: ' . PHP_EOL;
        $message_text .= $text;

        \Yii::$app->WhatsappNotification
            ->setMessageBody($message_text)
            ->setNoticeModule('weblogtarget')
            ->setActionRoute('systemlog')
            ->sendMessage();
    }
}

Step 2: Configure Log Target in config/web.php

'components' => [
    'log' => [
        'targets' => [
            [
                'class' => 'app\packages\components\WhatsappLogTarget',
                'levels' => ['error', 'warning'],
            ],
        ],
    ],
],

Now, all errors and warnings will be sent to WhatsApp.


Example: WhatsApp Notification Component

This component sends notifications to WhatsApp via an API.

Step 1: Create WhatsappNotification.php

namespace app\packages\components;

use Yii;
use app\models\WhatsappNotificationLog;

class WhatsappNotification extends \yii\base\Component
{
    private $_ENABLE_NOTIFICATION = false;
    private $_API_URL;
    private $_API_BEARER_TOKEN;
    private $_DEFAULT_MOBILE_NUMBER;
    private $_RECIPIENT_MOBILE_NUMBER;
    private $_messageBody;
    private $_actionRoute;
    private $_module = 'core';

    public function init()
    {
        $this->_ENABLE_NOTIFICATION = Yii::$app->params['whatsapp_notification_enable'];
        $this->_API_URL = Yii::$app->params['whatsapp_api_url'];
        $this->_API_BEARER_TOKEN = Yii::$app->params['whatsapp_api_bearer_token'];
        $this->_DEFAULT_MOBILE_NUMBER = Yii::$app->params['whatsapp_api_mobile_number'];
    }

    public function sendMessage()
    {
        if (!$this->_ENABLE_NOTIFICATION) return;

        $data = $this->prepareMessageData();
        $api_response = '';

        if (!empty($this->_API_BEARER_TOKEN) && !empty($this->_API_URL)) {
            $data_string = json_encode($data);
            $curl = curl_init();
            curl_setopt_array($curl, [
                CURLOPT_URL => $this->_API_URL,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_CUSTOMREQUEST => 'POST',
                CURLOPT_POSTFIELDS => $data_string,
                CURLOPT_HTTPHEADER => [
                    'Content-Type: application/json',
                    'Authorization: Bearer ' . $this->_API_BEARER_TOKEN,
                ],
            ]);
            $api_response = curl_exec($curl);
            curl_close($curl);
        }

        return $this->saveMessageLog($data, $api_response);
    }

    public function setRecipientMobileNumber($value)
    {
        $this->_RECIPIENT_MOBILE_NUMBER = $value;
        return $this;
    }

    public function setMessageBody($body)
    {
        $this->_messageBody = $body;
        return $this;
    }

    public function setActionRoute($value)
    {
        $this->_actionRoute = $value;
        return $this;
    }

    public function setNoticeModule($module)
    {
        $this->_module = $module;
        return $this;
    }

    private function prepareMessageData()
    {
        return [
            "messaging_product" => 'whatsapp',
            'recipient_type' => 'individual',
            'to' => $this->_RECIPIENT_MOBILE_NUMBER ?? $this->_DEFAULT_MOBILE_NUMBER,
            "type" => 'text',
            'text' => ['preview_url' => false, 'body' => $this->_messageBody],
        ];
    }

    private function saveMessageLog($sent_data, $result)
    {
        $log = new WhatsappNotificationLog();
        $log->module = $this->_module;
        $log->action = $this->_actionRoute;
        $log->recipient_mobile_number = $this->_RECIPIENT_MOBILE_NUMBER ?? $this->_DEFAULT_MOBILE_NUMBER;
        $log->message_body = json_encode($sent_data);
        $log->response = $result;
        $log->created_at = time();
        $log->save();

        return $log;
    }
}

Step 2: Use the Component

\Yii::$app->WhatsappNotification
    ->setMessageBody('Your order has been shipped!')
    ->setNoticeModule('order')
    ->setActionRoute('shipping')
    ->sendMessage();