accessing an array in a component from the layout in yii2 - yii2

I need to check the records in the notifications table at every page load of every controller.
So I wrote it in a component and the component is executed in the bootstraping process.
I need the notifications to be available in the layout so that i can show them in the notification menu.
below is what I have tried so far:
component:
namespace admin\components;
use Yii;
use yii\base\Component;
use admin\models\Notification;
class NotificationManager extends \yii\base\Component{
public function init() {
$notifications = Notification::find()->orderBy('id DESC')->asArray()->all();
//echo "<pre>"; print_r($notifications);exit;
if(count($notifications)>0){
foreach ($notifications as $notif) {
if($notif['type'] == 'courier')
$courier_notifications[] = $notif;
elseif($notif['type'] == 'order')
$order_notifications[] = $notif;
}
Yii::$app->view->params['courier_notifications'] = $courier_notifications;
Yii::$app->view->params['order_notifications'] = $order_notifications;
}
}
}
Layout:
$courier_notifications = $this->params['courier_notifications'];
I am not sure which part am I going wrong: in component or in the layout?
I appreciate your help.

Im not sure why your component execution during bootstrap fails to add the value to params.But believe it to be an overkill.
You can rather move the logic to component method and access in layout whenever necessary
Component.
namespace admin\components;
use Yii;
use yii\base\Component;
use admin\models\Notification;
class NotificationManager extends Component{
public function notifications($type = 'courier') {
$notifications = Notification::find()
->where(['type' => $type])
->orderBy('id DESC')
->asArray()->all();
return $notifications;
}
}
Add the component class under Components section in your config file
'notificationManager ' => [
'class' => 'admin\components\NotificationManager'
]
Layout
$courier_notifications = yii::$app->notificationManager->notifications('courier');

If you really want to go bootstrap mode, you need to implement yii\base\BootstrapInterface and put your logic in the bootstrap($app) method in order for the param to be available site-wide by setting the value of Yii::$app->params['notifications'] to the result of your logic.
Another common approach is to add a new method public function displayNotifications or whatever you want to name it, to your component, move all the logic in it and then in your layout/view etc., call it with Yii::$app->notificationManager->displayNotifications(). You can also pass additional parameters to it and enhance your logic.
notificationManager has to be replaced with the name you registered your custom component in the Yii app config (web.php for basic app, main.php for advanced app).
LE - If you only registered your component for bootstrap, you should also register it in the components array.
'notificationManager' => [
'class' => '\admin\components\NotificationManager'
]

Related

How do I override a widget method in Yii2?

I need to override the renderPageButton() method in the Yii2 LinkPager widget. The method documentation specifically says "You may override this method to customize the generation of page buttons" but I can't figure out how to do that. Thanks.
Overriding LinkPager can be done this way:
Create a new file ./widgets/MyLinkPager.php:
<?php
namespace app\widgets;
use yii\widgets\LinkPager;
class MyLinkPager extends LinkPager
{
protected function renderPageButtons()
{
// do whatever you want, it may help to
// copy code from parent::renderPageButtons()
// or even call
return parent::renderPageButtons();
}
}
And then use it this way in your view (see here: https://www.yiiframework.com/doc/guide/2.0/en/output-pagination):
use yii\widgets\LinkPager;
echo LinkPager::widget([
'pagination' => $pagination,
]);
The class you want to override is documented here.
You can override it in the following way:
Create a new directory in your yii2 app root folder, like widgets
Create a new php file (like MyLinkPager.php) and a new class in it (MyLinkPager) which extens yii\widgets\LinkPager
You can use "app\widgets" namespace (i.e. if you are working with the basic yii2 app)
In your class, implement only the function you want to override from the original class
Use your new class wherever you want instead of the original one

Upgrading Laravel 4.2 to 5.0, getting [ReflectionException] Class App\Http\Controllers\PagesController does not exist

I was updating my project from laravel 4.2 to laravel 5.0. But, after I am facing this error and have been trying to solve it for the past 4 hours.
I didn't face any error like this on the 4.2 version. I have tried composer dump-autoload with no effect.
As stated in the guide to update, I have shifted all the controllers as it is, and made the namespace property in app/Providers/RouteServiceProvider.php to null. So, I guess all my controllers are in global namespace, so don't need to add the path anywhere.
Here is my composer.json:
"autoload": {
"classmap": [
"app/console/commands",
"app/Http/Controllers",
"app/models",
"database/migrations",
"database/seeds",
"tests/TestCase.php"
],
Pages Controller :
<?php
class PagesController extends BaseController {
protected $layout = 'layouts.loggedout';
public function getIndex() {
$categories = Category::all();
$messages = Message::groupBy('receiver_id')
->select(['receiver_id', DB::raw("COUNT('receiver_id') AS total")])
->orderBy('total', 'DESC'.....
And, here is BaseController.
<?php
class BaseController extends Controller {
//Setup the layout used by the controller.
protected function setupLayout(){
if(!is_null($this->layout)) {
$this->layout = View::make($this->layout);
}
}
}
In routes.php, I am calling controller as follows :
Route::get('/', array('as' => 'pages.index', 'uses' => 'PagesController#getIndex'));
Anyone please help. I have been scratching my head over it for the past few hours.
Routes are loaded in the app/Providers/RouteServiceProvider.php file. If you look in there, you’ll see this block of code:
$router->group(['namespace' => $this->namespace], function($router)
{
require app_path('Http/routes.php');
});
This prepends a namespace to any routes, which by default is App\Http\Controllers, hence your error message.
You have two options:
Add the proper namespace to the top of your controllers.
Load routes outside of the group, so a namespace isn’t automatically prepended.
I would go with option #1, because it’s going to save you headaches in the long run.

Calling a Component function from helper

Some times ago I wrote a component that I find very convenient to use instead of other kind of authorization tools. I have converted it to CakePHP 3 and it still suits perfectly to my needs, but now I need to call one of its functions from a helper, and I can't figure out how to do that. The component name is PermissionsComponent.
Here is a draft of my helper:
namespace App\View\Helper;
use Cake\View\Helper;
use App\Controllers\Component\PermissionsComponent;
class PermissionsHelper extends Helper {
function check($action, $redirect = false) {
// how can I call my component's action check($action, $redirect)?
}
}
How can I call that component's action from a helper?
You can't. It sounds more like you should use another object that you can use in both the component and the helper.
// In PermissionsComponent
$permissions = new Permissions();
...
$this->_controller->set('_permissions', $permissions);
And then you can use it in your helper:
// In PermissionsHelper
$permissions = $this->_View->get('_permissions');

Tracking data on each request in Yii2

What will the best place in the code to track user's last visit date or any data that should be tracked on each request to application? Is it good idea to extend yii\web\Controller?
You can use a base controller and of course it is a good idea. But there is another approach that is more elegant. You can do like below:
1 - Add a component into your components directory, for example(MyTrackingClass):
namespace app\components;
class MyTrackingClass extends \yii\base\Component{
public function init() {
//SOME CODE HERE
//SOME CODE HERE
//SOME CODE HERE
parent::init();
}
}
2 - Add MyTrackingClass component into your components array in config file:
'components' => [
'MyTrackingClass'=>[
'class'=>'app\components\MyTrackingClass'
],
//other components
3 - Add MyTrackingClass into bootstarp array in config file:
'bootstrap' => ['log','MyTrackingClass'],
Now, you can see everything you wrote in your init() method, will be executed in every request, in every module, controller, action and so on...
Please note that, if you do not need to use Events and Behaviors you can use \yii\base\Object instead of \yii\base\Component

Execute my code before any action of any controller

I would like to check if my user have filled certain fields in his profile before he can access any action of any controller.
For example
if(empty(field1) && empty(field2))
{
header("Location:/site/error")
}
In yii1 I could do it in protected\components\Controller.php in init() function
But in yii2 I'm not sure where to put my code. I cannot modify core files, but not sure what to do in backend of my advanced application to make it work.
I know I can user beforeAction() but I have too many controllers to do that and to keep track of every controller
In case you need to execute a code before every controller and action, you can do like below:
1 - Add a component into your components directory, for example(MyGlobalClass):
namespace app\components;
class MyGlobalClass extends \yii\base\Component{
public function init() {
echo "Hi";
parent::init();
}
}
2 - Add MyGlobalClass component into your components array in config file:
'components' => [
'MyGlobalClass'=>[
'class'=>'app\components\MyGlobalClass'
],
//other components
3 - Add MyGlobalClass into bootstarp array in config file:
'bootstrap' => ['log','MyGlobalClass'],
Now, you can see Hi before every action.
Please note that, if you do not need to use Events and Behaviors you can use \yii\base\Object instead of \yii\base\Component
Just add in config file into $config array:
'on beforeAction' => function ($event) {
echo "Hello";
},
Create a new controller
namespace backend\components;
class Controller extends \yii\web\Controller {
public function beforeAction($event)
{
..............
return parent::beforeAction($event);
}
}
All your controllers should now extend backend\components\Controller and not \yii\web\Controller. with this, you should modify every controller. I would go for this solution.
I believe you might also replace 1 class with another (so no change to any controller necessary), something like
\Yii::$classMap = array_merge(\Yii::$classMap,[
'\yii\web\Controller'=>'backend\components\Controller',
]);
See more details here: http://www.yiiframework.com/doc-2.0/guide-tutorial-yii-integration.html and I took the code from here: https://github.com/mithun12000/adminUI/blob/master/src/AdminUiBootstrap.php
you can put this in your index.php file. However, make sure you document this change very well as somebody that will come and try to debug your code will be totally confused by this.
Just i think this code on config file can help you:
'on beforeAction' => function ($event) {
// To log all request information
},
'components' => [
'response' => [
'on beforeSend' => function($event) {
// To log all response information
},
],
];
Or, https://github.com/yiisoft/yii2/blob/master/docs/guide/security-authorization.md use RBAC, to restrict access to controllers actions one at a time based on rules. Why would you want to restrict access to controller actions based on user fields is beyond me. You will not be able to access anything (including the login form) if you put a restriction there.