Zend Framework: How to stop dispatch/controller execution? - ajax

I have a Zend Framework controller with an editAction().
class WidgetController extends BaseController
{
public function editAction()
{
//code here
}
}
This controller extends a base controller which checks if the user is logged in before allowing the user to edit a record.
class BaseController extends Zend_Controller_Action
{
public function init()
{
if ($this->userNotLoggedIn()) {
return $this->_redirect('/auth/login');
}
}
}
However, now that I am performing an AJAX request, I will be sending a JSON response back, so a redirect will no longer work. I need to stop further controller execution so I can immediately send a response:
class BaseController extends Zend_Controller_Action
{
public function init()
{
if ($this->userNotLoggedIn()) {
if ($this->_request->isXmlHttpRequest()) {
$jsonData = Zend_Json::encode(array('error'=>'You are not logged in!'));
$this->getResponse()
->setHttpResponseCode(401)
->setBody($jsonData)
->setHeader('Content-Type', 'text/json');
//now stop controller execution so that the WidgetController does not continue
} else {
return $this->_redirect('/auth/login');
}
}
}
}
How can I stop controller execution?

I would define the user not being logged in and trying to make an XMLHTTPRequest as an exceptional state and let the error handler deal with it by throwing an exception (which stops dispatching of the current action). That way you are also able to handle other kinds of exceptions that might happen:
class BaseController extends Zend_Controller_Action
{
public function init()
{
if ($this->userNotLoggedIn()) {
if ($this->_request->isXmlHttpRequest()) {
throw new Exception('You are not logged in', 401);
} else {
return $this->_redirect('/auth/login');
}
}
}
}
class ErrorController extends Zend_Controller_Action
{
public function errorAction()
{
$errors = $this->_getParam('error_handler');
$exception = $errors->exception;
if ($this->_request->isXmlHttpRequest()) {
$jsonData = Zend_Json::encode($exception);
$jsonData = Zend_Json::encode(array('error'=> $exception->getMessage()));
$isHttpError = $exception->getCode() > 400 && $exception->getCode();
$code = $isHttpError ? $exception->getCode() : 500;
$this->getResponse()
->setHttpResponseCode($code)
->setBody($jsonData)
->setHeader('Content-Type', 'application/json');
} else {
// Render error view
}
}
}

I can think of many ways to stop the controller at this point in your code.
//now stop controller execution so that the WidgetController does not continue
For one, you can replace that line with this the following:
$this->getResponse()->sendResponse();
exit;
That may not be the cleanest but gets the job done rather nicely. The other option is going to be to change the action of the request in the init and let another action handle it. Replace that line with this:
$this->getRequest()->setActionName('invalid-user');
Because your already inside the dispatcher, it's going to run an action inside your action class whether you want it to or not. Trying to change the request in preDispatch will do nothing to change this dispatch. It's determined at this point to run an action inside your class. So, make an action to handle it.
public function invalidUserAction()
{
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
}
For more information see Zend_Controller_Dispatcher_Standard::dispatch.

Related

Laravel OBSERVER update process

In SESSObServer
public function updated(SESS $sESS)
{
log::info("data updated");
if ($sESS->wasChanged('is_active')) {
log::info("data updated");
}
}
IN Observer, created(SESS $sESS) function run but update is not run. How can i solve problem ?
I changed my query. Now, this is run.
I didn't use get. I used first()
$sess = SESS::where('sess_id', request('id'))->first();
$sess->save();
Make sure your Observer is registered.
# app/Providers/EventServiceProvider.php
use App\Models\SESS;
use App\Observers\SESSObserver;
class EventServiceProvider extends ServiceProvider
{
public function boot()
{
SESS::observe(SESSObServer::class);
}
}
Make sure you're using the correct classes. The logger's class is Log (capital L)
public function updated(SESS $sESS)
{
Log::info("data updated");
if ($sESS->wasChanged('is_active')) {
Log::info("data updated");
}
}
Alternatively, I think you can still use info().
public function updated(SESS $sESS)
{
info("data updated");
if ($sESS->wasChanged('is_active')) {
info("data updated");
}
}
If you're using apache, make sure the apache user can write to the log file.

Laravel mock multiple dependency

I have a Controller that has a dependency with BillingService, and BillingService has another dependency on UserService.
I need to call the Controller method getPlans and in this call I need to mock two functions:
loadPlans that is inside BillingService
getUsage that is in UserService
This is the full example:
class BillingPlanController
{
public function __construct(private BillingPlanService $billingPlanService)
{
}
public function getPlans()
{
$plans = $this->billingPlanService->getPlans();
//
}
}
class BillingPlanService
{
public function __construct(private UserService $userService)
{
}
public function getPlans()
{
$plans = $this->loadPlans();
$user = auth()->user();
$usage = $this->userService->getUsage(user); // DO SOMETHING, NEED TO MOCK .. HOW ?
}
public function loadPlans()
{
// DO SOMETHING, NEED TO MOCK .. HOW ?
}
}
At the end, in my test i simply call:
getJson(action([BillingPlanController::class, "getPlans"]));
In other tests, I'm able to mock a single Service, but in this scenario, I don't know how to write the mocks.
Sorry if I don't provide any "tries", but I really don't know how I can do that.
UPDATE
I tried to use partialMock and mock, but I get this error (when getUsage is called) - partialMock is used because i just need to mock a single function:
Typed property App\Modules\Billing\Services\BillingPlanService::$userService must not be accessed before initialization
$this->mock(UserService::class, function ($mock) {
$mock->shouldReceive("getUsage")->andReturn([]);
});
$this->partialMock(BillingPlanService::class, function ($mock) {
$mock->shouldReceive("loadPlans")->andReturn([]);
});
getJson(action([BillingPlanController::class, "getPlans"]));
Your exception in your partial mock, is because when you mock the BillingPlanService you do not intilize the userService due to it being a mock. You can simply set it on the mock and i think it should work in your context.
$userServiceMock = $this->mock(UserService::class, function ($mock) {
$mock->shouldReceive("getUsage")->andReturn([]);
});
$this->partialMock(BillingPlanService::class, function ($userServiceMock) use ($userServiceMock) {
$mock->set('userService', $userServiceMock);
$mock->shouldReceive("loadPlans")->andReturn([]);
});

How to set listener queue name from environment variable?

I just noticed that some of my listeners do not use the queue I expected them to use. Our team upgraded from Laravel 5.2 to 5.5 a few weeks back, and I guess this is when the problem started happening. There hasn't been much load on the system, so I only discovered it by accident.
Anyway. I used to set the queue name on the listener through a queue method, like so:
public function queue(QueueManager $handler, $method, $arguments): void
{
$handler->connection()->push($method, $arguments, Queue::getNotificationQueue());
}
This approach is not working anymore, so a default queue ends up handling the job instead of the expected notification queue.
So I looked at the documentation https://laravel.com/docs/5.5/events#queued-event-listeners, which states that the name should be set on a queue property on the listener. My problem is that I have the queue name in an environment variable, so I cannot just set it directly as a property, as shown in the documentation and it does not work to set it on the constructor, like so:
protected $queue;
public function __construct()
{
$this->queue = Queue::getNotificationQueue();
}
Does anyone here have an idea of how I can get around this?
Specifically for SQS queues the $queue property acts a bit weird because it doesn't seem to refer to queues defined in queue.php, but it expects a full queue url, so even the example in the documentation seems off.
But for dynamic queue names on queued event listeners that for example changes depending on environment, making a custom SqsConnector and SqsQueue will be one way to solve your issue.
Here is an example of implementation.
ACMEEventListener.php
class ACMEEventListener implements ShouldQueue
{
public function handle(Event $event): void
{
// I'm going to a custom queue
}
public static function getQueue(): string
{
return 'https://sqs.eu-central-1.amazonaws.com/<account id>/<queue name>';
}
}
CustomSqsConnector.php
use Illuminate\Queue\Connectors\SqsConnector;
use Aws\Sqs\SqsClient;
class CustomSqsConnector extends SqsConnector
{
public function connect(array $config)
{
$sqs = new SqsClient($config);
return new CustomSqsQueue($sqs, $config['queue']);
}
}
CustomSqsQueue.php
class CustomSqsQueue extends \Illuminate\Queue\SqsQueue
{
public function push($job, $data = '', $queue = null)
{
if ($job instanceof CallQueuedListener && method_exists($job->class, 'getQueue')) {
$queue = $job->class::getQueue();
}
return $this->pushRaw($this->createPayload($job, $data), $queue);
}
}
CustomSqsQueueServiceProvider.php
class CustomSqsQueueServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->booted(function () {
$this->app['queue']->extend('custom_sqs', function () {
return new CustomSqsConnector;
});
});
}
}
And then in your queue.php, your default SQS connection driver from sqs to custom_sqs

one controller for three pages

I have one controller ADD. I want this controller to manipulate three pages: add_customer page, add_project page and add_post_page.In this case add_project page works perfectly. But add_customer and add_post pages have errors:
Failed to load resource: the server responded with a status of 500 (Internal Server Error).
I think the problem is in my coditions here:
public function index(){
if($this->uri->segment(3)=='add_customer'){
$this->add_customer();
}
else if($this->uri->segment(3)=='add_post'){
$this->add_post();
}
else{
$this->add_project();
}
}
This is my full controller:
class ADD extends MX_Controller {
public $mname, $tag, $tpl;
function __construct()
{
$this->mname=strtolower(get_class());
$this->tag=strtoupper($this->mname);
}
public function index(){
if($this->uri->segment(3)=='add_customer'){
$this->add_customer();
}
else if($this->uri->segment(3)=='add_post'){
$this->add_post();
}
else{
$this->add_project();
}
}
public function add_project()
{
include APPPATH."language/".LANG.".php";
$this->load->model($this->mname.'/add_project_model');
$model='add_project_model';
$this->$model->index($this->mname);
$a['IsEnabled']=$LANGUAGE['IsEnabled'];
$a['Submit']=$LANGUAGE['Submit'];
$a['Cancel']=$LANGUAGE['Cancel'];
$a['Reset']=$LANGUAGE['Reset'];
$a['Name']=$LANGUAGE['Name'];
$a['SelectCustomer']=$LANGUAGE['SelectCustomer'];
$a['Project Name']=$LANGUAGE['ProjectName'];
$a['Manager']=$LANGUAGE['Manager'];
$a['Customer']=$LANGUAGE['Customer'];
$userGROUP = $this->session->userdata('_userGROUP');
if ($userGROUP=='Administrator')
$a['AddManager']='<button type="button" class="btn btn-warning" onclick="AddNewManager()">+</button>';
else
$a['AddManager']='';
$this->tp->assign($a);
$this->tp->parse('CONTENT', $this->mname.'/add_project.tpl');
}
public function add_customer()
{
include APPPATH."language/".LANG.".php";
$userGROUP = $this->session->userdata('_userGROUP');
if($userGROUP!=='Administrator')
{
show_404('page');
exit;
}
$this->load->model($this->mname.'/add_customer_model');
$model='add_customer_model';
$this->$model->index($this->mname);
$a['IsEnabled']=$LANGUAGE['IsEnabled'];
$a['Submit']=$LANGUAGE['Submit'];
$a['Cancel']=$LANGUAGE['Cancel'];
$a['Reset']=$LANGUAGE['Reset'];
$a['Name']=$LANGUAGE['Name'];
$a['Project Name']=$LANGUAGE['CustomerName'];
$a['Customer Name']=$LANGUAGE['Customer Name'];
$this->tp->assign($a);
$this->tp->parse('CONTENT', $this->mname.'/add_customer.tpl');
}
public function add_post()
{
include APPPATH."language/".LANG.".php";
$userGROUP = $this->session->userdata('_userGROUP');
if($userGROUP=='Engineer')
{
show_404('page');
exit;
}
$this->load->model($this->mname.'/add_post_model');
$model='add_post_model';
$this->$model->index($this->mname);
$a['IsEnabled']=$LANGUAGE['IsEnabled'];
$a['Submit']=$LANGUAGE['Submit'];
$a['Cancel']=$LANGUAGE['Cancel'];
$a['Reset']=$LANGUAGE['Reset'];
$a['Activity Name']=$LANGUAGE['Activity Name'];
$a['SalaryHour']=$LANGUAGE['SalaryHour'];
$this->tp->assign($a);
$this->tp->parse('CONTENT', $this->mname.'/add_post.tpl');
}
}
How can I resolve this problem?
Just Give it a Try, Hope it works :
public function index(){
if($this->uri->segment(3)=='add_customer'){
redirect(base_url().'/Add/add_customer');
//$this->add_customer();
}
else if($this->uri->segment(3)=='add_post'){
redirect(base_url().'/Add/add_post');
//$this->add_post();
}
else{
redirect(base_url().'/Add/add_project');
//$this->add_project();
}
}
Your index function is never reached
This would reach it : http://project.dev/add
By default the following urls hit the appropriate function.
http://project.dev/add/add_customer hit public function add_customer()
http://project.dev/add/add_post hit public function add_post()
If you are getting errors check out the various error in the specific function and make sure you have error reporting turned on

How to call dispatchShell on same shell for another method?

I have a cake shell, say DemoShell. And, from a method 'method1', I want to create another process which executes 'method2' of DemoShell. How can that be done?
class DemoShell extends AppShell {
// some code
private function method1() {
// some code
$success = $this->dispatchShell('Demo', 'method2', $params);
// some more code
}
private function method2() {
// some code to do something and return boolean value
}
}
I tried this, but method2 isn't getting called.

Resources