I cannot seem to get my events to fire off. This is my first time playing around with them. If you look, i create an event in the init method and then i try to fire it off in the onBootstrap method. The event should produce a die with the string, but its not. any help would be appreciated.
namespace Application;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ControllerProviderInterface;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\ModuleManager\Feature\ServiceProviderInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\Feature\InitProviderInterface;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
use Zend\ModuleManager\ModuleManagerInterface;
use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
use Zend\EventManager\EventInterface;
use Doctrine\DBAL\DriverManager;
class Module implements
AutoloaderProviderInterface,
ControllerProviderInterface,
BootstrapListenerInterface,
ServiceProviderInterface,
ConfigProviderInterface,
InitProviderInterface,
ViewHelperProviderInterface
{
public function init(ModuleManagerInterface $manager)
{
$eventManager = $manager->getEventManager();
$eventManager->attach('do',function($e){
$event = $e->getName();
$target = get_class($e->getTarget());
$params = $e->getParams();
$str = sprintf(
'Handled event \"%s\" on target \"%s\", with parameters %s',
$event,
$target,
json_encode($params)
);
die($str);
});
}
public function onBootstrap(EventInterface $e)
{
$this->attachEventManagerToModuleRouteListener($e);
$this->setupDoctrineConnectionMappings($e);
$eventManager = $e->getApplication()->getEventManager();
$eventManager->trigger('do',$this,array('j','o','n'));
}
public function getConfig()
{
return array_merge(
include __DIR__ . '/config/module.config.php',
include __DIR__ . '/config/routes.config.php'
);
}
public function getServiceConfig()
{
return include __DIR__ . '/config/services.config.php';
}
public function getControllerConfig()
{
return include __DIR__ . '/config/controllers.config.php';
}
public function getViewHelperConfig()
{
return include __DIR__ . '/config/view.config.php';
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__
),
),
);
}
private function attachEventManagerToModuleRouteListener(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
}
private function setupDoctrineConnectionMappings(MvcEvent $e)
{
$driver = $e->getApplication()->getServiceManager()->get('doctrine.connection.orm_default');
$platform = $driver->getDatabasePlatform();
$platform->registerDoctrineTypeMapping('enum', 'string');
$platform->registerDoctrineTypeMapping('set', 'string');
}
}
The EventManager you're getting from the ModuleManager is a different EventManager than the Application's EventManager which you let trigger the event do.
Since during Module initialization the Application is not yet available, you've to bind your listener to the event via the SharedManager.
So attaching to Application's event do would go like this
$sharedManager = $manager->getEventManager()->getSharedManager();
$sharedManager->attach(Application::class, 'do', function($e) {
// event code
});
Please note that the shared manager requires the (an) identifier of the EventManager that's expected to trigger the event which in this case is (and often is) the classname of Application.
Related
I need to recreate a resquest so that it behaves like a call via api to go through the validator, but my $request->input('rps.number') always arrives empty, although I can see the data in the debug
I also couldn't get it to go through the laravel validator
I can't use a technique to make an http call, because I need to put this call in a transaction
<?php
$nota = new stdClass();
$rps = new stdClass();
$rps->numero = (int)$xml->Rps->IdentificacaoRps->Numero;
$rps->serie = (string)$xml->Rps->IdentificacaoRps->Serie;
$rps->tipo = (int)$xml->Rps->IdentificacaoRps->Tipo;
$nota->rps = $rps;
$controller = new NotaController(new Nota());
$content = new StoreNotaRequest();
$content->request->add($nota);
$result = $controller->store($content);
StoreNotaRequest
<?php
class StoreNotaRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
$request = $this->request;
return [
'rps.numero' => 'required_with:rps|numeric|between:1,999999999999999',
'rps.serie' => 'required_with:rps|string|min:1|max:5',
'rps.tipo' => 'required_with:rps|integer|in:1,2,3'
];
}
}
NotaController
<?php
class NotaController extends Controller
{
private Nota $nota;
public function __construct(Nota $nota)
{
$this->nota = $nota;
}
public function store(StoreNotaRequest $request): JsonResponse
{
// $validated = $request->validated();
try {
$nota = DB::transaction(function () use ($request) {
$request->input('rps.numero');
});
return response()->json($nota);
} catch (Throwable $e) {
return response()->json($data, 409);
}
}
}
Solution
the solution was a little too verbose, I believe it is possible to solve with less code.
more does what it needs to go through the validation of the data contained in the StoreNotaRequest
and it returns an http response, in addition to being able to put all these isolated calls in a single transaction
DB::beginTransaction();
$errors = [];
foreach ($itens as $item) {
$controller = new NotaController(new Nota());
$request = new StoreNotaRequest();
$request->setMethod('POST');
$request->request->add($nota);
$request
->setContainer(app())
->setRedirector(app(Redirector::class))
->validateResolved();
$response = $controller->store($request);
if ($response->statusText() !== 'OK') {
$errors[$item->id] = 'ERROR';
}
}
if (count($errors) === 0) {
DB::commit();
} else {
DB::rollBack();
}
I have a bit of a dilemma as I need to come up with a good logger that logs what is happening in the app at the same time if there is a Log::error called, it should also notify Devs and Sys admin via slack. It is currently working, but it adds an overhead to the request-response time.
Below is my setting:
//config/logging.php
'default' => env('LOG_CHANNEL', 'stack'),
//truncated
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 0,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'App',
'emoji' => ':boom:',
'level' => 'error',
]
]
//truncated
//UserController
public function show(User $user)
{
//just a sample code, the important part is where the Log facade is called
try {
//business logic
} catch (Exception $e) {
Log::error(get_class(), [
'user_id' => $user->id,
'message' => $e->getMessage()
]);
}
return view('user.show', compact($user));
}
It is already working, but for sure we can still improve this to reduce the overhead somehow even though the added time for code above is negligible, but the real code is more complex and has quite a lot of iteration
How can I alter modify the behavior of the 'slack' logger to push it into a queue when it is triggered? I prefer to code it once and forget it rather than remembering that I have to push it to an on-demand logger such as
Log::chanel(['daily', 'slack'])->...
OR
//this is good for more on particular event notification but not not error notification which can happen anywhere
Notification::route('slack', env('LOG_SLACK_WEBHOOK_URL'))->notify(new AlertDevInSlackNotification)`
Note:
I tried adding some code into bootstrap/app.php but it is not working
//bootstrap/app.php
$app->configureMonologUsing(function($monolog) use ($app) {
//some code here. does not work, just getting page not working
});
It is like when I call this log level and this channel, I want it to be queued
You can do like this.
1.Create Job ex: name as LogSlackQueue.php
public class LogSlackQueue implements ShouldQueue {
...
...
public function handle() {
Log::channel(['daily', 'slack'])->info($your_input);
}
}
2.Then use as
LogSlackQueue::dispatch($your_input)
If you dont want to do like above, you need to figure it out to make custom provider
Thanks to #ZeroOne for giving out idea on how to solve it. I wanted it automatic and any existing code having Log::error() will automatically prompt the devs.
Below is my solution.
//CustomSlackServiceProvider.php
try {
//listen to all events
Event::listen('*', function($event, $details) {
//check if the event message logged event which is triggered when we call Log::<level>
if($event == "Illuminate\Log\Events\MessageLogged") {
//$details contain all the information we need and it comes in array of object
foreach($details as $detail) {
//check if the log level is from error to emergency
if(in_array($detail->level, ['emergency', 'critical', 'alert', 'error'])) {
//trigger the notification
Notification::route('slack', env('LOG_SLACK_WEBHOOK_URL'))->notify(new AlertDevInSlackNotification($detail->message, $detail->level, $detail->context));
}
}
}
});
} catch (Exception $e) {
}
//AlertDevInSlackNotification.php
class AlertDevInSlackNotification extends Notification implements ShouldQueue
{
use Queueable;
private $class;
private $level;
private $context;
public function __construct($class, $level, $context)
{
$this->class = $class;
$this->level = strtoupper($level);
$this->context = $context;
//prevent congestion in primary queue - make sure this queue exists
$this->queue = 'alert';
}
public function via($notifiable)
{
return ['slack'];
}
public function toSlack($notifiable)
{
return (new SlackMessage)
->content($this->level.': '.$this->class)
->attachment(function($attachment) {
$attachment->fields($this->context);
});
}
UPDATE:
The code above will work when you trigger Log::error().
But to listen to an event that is being thrown by an error such as syntax error which will cause "Serialization of 'Closure' is not allowed". You can do this instead to improve coverage:
public function boot()
{
try {
//listen to all events
Event::listen('*', function($event, $details) {
//check if the event message logged event which is triggered when we call Log::<level>
if($event == "Illuminate\Log\Events\MessageLogged") {
// dump($event);
//$details contain all the information we need and it comes in array of object
foreach($details as $detail) {
$this->path = '';
$this->level = '';
$this->context = [];
$this->message = '';
//check if the log level is from error to emergency
if(in_array($detail->level, ['emergency', 'critical', 'alert', 'error'])) {
//#todo - exclude: Error while reading line from the server. [tcp://cache:6379] = restart
//check if the context has exception and is an instance of exception
//This is to prevent: "Serialization of 'Closure' is not allowed" which prevents jobs from being pushed to the queue
if(isset($detail->context['exception'])) {
if($detail->context['exception'] instanceof Exception) {
$this->level = $detail->level;
//to keep consistency on all the log message, putting the filename as the header
$this->message = $detail->context['exception']->getFile();
$this->context['user'] = auth()->check() ? auth()->user()->id.' - '. auth()->user()->first_name.' '.auth()->user()->last_name : null;
$this->context['message'] = $detail->context['exception']->getMessage();
$this->context['line'] = $detail->context['exception']->getLine();
$this->context['path'] = request()->path();
$this->runNotification();
continue;
}
}
$this->level = $detail->level;
$this->context = $detail->context;
$this->message = $detail->message;
$this->runNotification();
continue;
}
}
}
});
} catch (Exception $e) {
}
}
public function runNotification()
{
Notification::route('slack', env('LOG_SLACK_WEBHOOK_URL'))->notify(new AlertDevInSlackNotification($this->message, $this->level, $this->context));
}
I am getting error -
localhost redirected you too many times.
in hooks
This is my code in hooks.I am unable to resolve this issue please help me
and please explain how to use hooks in ci and i am using post_controller_constructor
<?php
class Example {
private $CI;
function __construct()
{
$this->CI =& get_instance();
if(!isset($this->CI->session)){ //Check if session lib is loaded or not
$this->CI->load->library('session'); //If not loaded, then load it here
}
//echo "class".$this->CI->router->class; die;
if ( $this->CI->router->class == 'student' )
{
return;
}
}
public function check_login()
{
echo $session_userdata = $this->CI->session->userdata('email');
echo "session data".$session_userdata;
// die;
if(empty($session_userdata)) {
redirect("student/index");
}
else {
echo "here";
}
}
}
?>
i'm guessing your hook array looks like
$hook['post_controller_constructor'] = array(
array(
'class' => 'Example',
'function' => 'check_login',
'filename' => 'Example.php',
'filepath' => 'hooks'
),
);
It doesn't really matter whether you return anything in your constructor or not - a constructor serves only one purpose - to instantiate their respective class.
So basically you have to do something like
class Example {
private $CI;
function __construct()
{
$this->CI =& get_instance();
if(!isset($this->CI->session)){ //Check if session lib is loaded or not
$this->CI->load->library('session'); //If not loaded, then load it here
}
}
public function check_login()
{
echo $session_userdata = $this->CI->session->userdata('email');
echo "session data".$session_userdata;
// die;
if(empty($session_userdata) && $this->CI->router->class !== 'student') {
redirect("student/index");
}
else {
echo "here";
}
}
}
First of all, if you are using hooks in the CodeIgniter for the first time then follow some steps.
Step1: Enabling Hooks
The hooks feature can be globally enabled in the application/config/config.php file
Open config.php file does following replaces
$config['enable_hooks'] = FALSE;
To
$config['enable_hooks'] = TRUE;
Step2: Defining a Hook
Hooks are defined in the application/config/hooks.php file.
$hook['post_controller'] = array(
'class' => 'firstHookFile',
'function' => 'checkHook',
'filename' => 'firstHookFile.php',
'filepath' => 'hooks'
);
Step3: Create hook file
Create file in the application/hooks.
<?php
class firstHookFile
{
public function checkHook()
{
// load the instance
$this->CI =& get_instance();
$this->CI->load->helper('uri');
echo base_url();
return;
}
}
?>
This hook is only for example purpose, this hook will return the base URL of your project.
I hope you have understood how to use hooks in Codeigniter.
Has anyone ever needed to bind an ActiveRecord event handler in a way that it only triggers on certain scenarios?
In an ideal world the ActiveRecord on() method would also take a $scenarios parameter and trigger the handler only if the ActiveRecord is using that scenario at the time the event occurs. But since that is not the case I am looking for a clean, reliable way to implement this type of functionality.
Edit: This needs to work with any event, including built-in events triggered by the Yii framework (eg. ActiveRecord::EVENT_AFTER_INSERT).
This improvement of my first comment.
class ScenarioEvent extends \yii\base\Event
{
private static $_events = [];
public static function onScenario($scenario, $class, $name, $handler, $data = null)
{
// You may use foreach if you wil use many scenarios for event
if (!isset(self::$_scenarios)) {
self::$_scenarios[$scenario] = [];
}
self::$_scenarios[$scenario][$name][] = $class;
return static::on($class, $name, $handler, $data);
}
public static function trigger($class, $name, $event = null)
{
if (
// If event is null assumed that it has no sender with scenario
$event === null
|| empty(self::$_scenarios[$event->sender->scenario][$name])
) {
parent::trigger($class, $name, $event);
} else {
$classes = array_merge(
[$class],
class_parents($class, true),
class_implements($class, true)
);
foreach ($classes as $class) {
if (in_array($class, self::$_scenarios[$event->sender->scenario][$name])) {
parent::trigger($class, $name, $event);
break;
}
}
}
}
}
I got a problem rendering nested view, here is what I'm trying to do
I changed your 'request' of HMVC (HMVC-GitHub or/and HMVC-Pattern) function into an Elements module
namespace Modules\Main\Libraries;
/**
* Elements
*
* Helps to build UI elements for the application
*/
class Elements extends \Phalcon\Mvc\User\Component
{
public function loadModule($path = '', $data = array()) {
$di = clone $this->getDI();
$dispatcher = $di->get('dispatcher');
$paths = explode('/', $path);
$data = is_array($data) ? $data : array($data);
// get controller name
if (isset($paths[0])) {
$controller = $paths[0];
}
// get action name
if (isset($paths[1])) {
$action = $paths[1];
}
// get params
if (isset($paths[2])) {
array_splice($paths, 0, 2);
$params = array_merge($paths, $data);
} else {
$params = $data;
}
if (!empty($controller)) {
$dispatcher->setControllerName($controller);
} else {
$dispatcher->setControllerName('index');
}
if (!empty($action)) {
$dispatcher->setActionName($action);
} else {
$dispatcher->setActionName('index');
}
if (!empty($params)) {
if(is_array($params)) {
$dispatcher->setParams($params);
} else {
$dispatcher->setParams((array) $params);
}
} else {
$dispatcher->setParams(array());
}
$dispatcher->dispatch();
$response = $dispatcher->getReturnedValue();
if ($response instanceof ResponseInterface) {
return $response->getContent();
}
return $response;
}
}
and I have 2 controllers:
namespace Modules\Main\Controllers;
class IndexController extends ControllerBase
{
public function indexAction()
{
$secondContent = $this->elements->loadModule('test/hello/json');
$this->view->setVar('secondContent', $secondContent);
}
}
and
namespace Modules\Main\Controllers;
use \Phalcon\Http\Response;
class TestController extends ControllerBase
{
public function indexAction()
{
}
public function helloAction($format='html', $param = 'empty')
{
$this->view->setVar('content', 'Hello this is test value "'.$param.'"');
$content = $this->view->getContent();
return (string)$content;
// return 'Hello this is test value "'.$param.'"';
}
}
my DI
$di['elements'] = function() {
return new \Modules\Main\Libraries\Elements();
};
Views files
IndexController::Index
<h1>Congratulations!</h1>
<p>You're now flying with Phalcon. Great things are about to happen!</p>
<p>Second content: {{ secondContent}}</p>
<p>HMVC: {{ elements.loadModule('test/hello/json', 'test') }}</p>
and HelloController::test
This is :: {{ content }}
expecting to get
Congratulations!
You're now flying with Phalcon. Great things are about to happen!
Second content: This is :: Hello this is test value "empty"
HMVC: This is :: Hello this is test value "test"
but it only rendering the HelloController (First call from IndexController::indexAction):
This is :: Hello this is test value "empty"
if I change IndexController::indexAction to
public function indexAction()
{
$secondContent = '';
$this->view->setVar('secondContent', $secondContent);
}
and TestController::helloAction to
public function helloAction($format='html', $param = 'empty')
{
$this->view->setVar('content', 'Hello this is test value "'.$param.'"');
$content = $this->view->getContent();
//return (string) $content;
return 'Hello this is test value "'.$param.'"';
}
the result that i get is (Second content is empty):
Congratulations!
You're now flying with Phalcon. Great things are about to happen!
Second content:
HMVC: Hello this is test value "test"
Any solution to solve this ?
Thanks,
Helman
Phalcon have built-it modules feature, you dont have to built your own module loader, you just need create module bootstrap that extend ModuleDefinitionInterface.
Just take a look this sample from phalcon multi module
https://github.com/phalcon/mvc/tree/master/multiple
this example below is taken from link above, This contain module bootstrap code.
<?php
namespace Multiple\Frontend;
class Module
{
public function registerAutoloaders()
{
$loader = new \Phalcon\Loader();
$loader->registerNamespaces(array(
'Multiple\Frontend\Controllers' => '../apps/frontend/controllers/',
'Multiple\Frontend\Models' => '../apps/frontend/models/',
));
$loader->register();
}
/**
* Register the services here to make them general or register in the ModuleDefinition to make them module-specific
*/
public function registerServices($di)
{
//Registering a dispatcher
$di->set('dispatcher', function () {
$dispatcher = new \Phalcon\Mvc\Dispatcher();
//Attach a event listener to the dispatcher
$eventManager = new \Phalcon\Events\Manager();
$eventManager->attach('dispatch', new \Acl('frontend'));
$dispatcher->setEventsManager($eventManager);
$dispatcher->setDefaultNamespace("Multiple\Frontend\Controllers\\");
return $dispatcher;
});
//Registering the view component
$di->set('view', function () {
$view = new \Phalcon\Mvc\View();
$view->setViewsDir('../apps/frontend/views/');
return $view;
});
$di->set('db', function () {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
});
}
}
you can load module using this code below
$app = new \Phalcon\Mvc\Application();
$app->registerModules(array(
'frontend' => array(
'className' => 'Multiple\Frontend\Module',
'path' => '../apps/frontend/Module.php'
),
'backend' => array(
'className' => 'Multiple\Backend\Module',
'path' => '../apps/backend/Module.php'
)
));