Cakephp 3 - var not transmitted to view when exception thrown - error-handling

In my AppController, I define a variable that must be used in every views (including error400.ctp, error500.ctp) of my app:
// /src/Controller/AppController.php
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
$foo = 'bar';
$this->set(compact('foo'));
}
It works well, except when an exception is thrown (like NotFoundException): I get the following error:
Undefined variable: foo in /src/Template/Error/error400.ctp
Is this a normal behavior of cakephp? How could I fix this?

Yes, this is normal behaviour, what is basically happening:
Exception is thrown (beforeFilter was called depending on where was it thrown, e.g. it is called for MissingAction or MissingTemplate, but not for MissingController).
Request handling is aborted and ErrorHandler steps in to catch and handle that Exception.
To render the Exception, ErroHandler uses ExceptionRenderer, which in turn creates special ErrorController, that kind of replaces original controller. This also means, that now you have completely different controller handling the request (new instance of Controller class), so even if beforeFilter was called and $foo was set, it is not longer valid.
ExceptionRenderer will use its own render() method to create output for error page.
To customize this, you can extend that default ExceptionRenderer, so you will be able to set variables to ErrorController
<?php
// goes in src/Error/AppExceptionRenderer
namespace App\Error;
use Cake\Error\ExceptionRenderer;
class AppExceptionRenderer extends ExceptionRenderer
{
public function render()
{
$this->controller->set('foo', 'bar');
return parent::render();
}
}
Set this class as default ExceptionRenderer in app.php
//...
'Error' => [
// ...
'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
// ...
],
So you need to set that global view variable in two places. Use some common method from models, Configure class to read global variables or whatever is suitable for your requirements.
More on custom error handling: Extending and Implementing your own Exception Handlers

Related

Angular update 8.1 to 8.2 I get many "Cannot read property 'ngInjectableDef' of undefined"

After updating from 8.1 to 8.2 I get many errors like this
MyComponent.html:6 ERROR TypeError: Cannot read property 'ngInjectableDef' of undefined
at getInjectableDef (core.js:361)
at resolveNgModuleDep (core.js:30377)
at NgModuleRef_.get (core.js:31578)
at injectInjectorOnly (core.js:734)
at ɵɵinject (core.js:744)
at injectArgs (core.js:837)
at core.js:16346
at _callFactory (core.js:30486)
at _createProviderInstance (core.js:30429)
at resolveNgModuleDep (core.js:30388)```
I found one reason...
I had many places like this
export class AbstractSth {
constructor(protected service: SomeService) {}
}
export class SpecialSth extends AbstractSth {
// ... special stuff (no constructor needed in 8.1)
}
Well it turned out that with 8.2 the SpecialSth class needs its own constructor with a super() call, because somehow now only the child class gets the needed properties injected and with no constructor nothing is injected and the inherited parent constructor seems to be ignored in the injection logic.
So this fixes the problem:
export class SpecialSth extends AbstractSth {
constructor(protected service: SomeService) {
super(service);
}
// ... special stuff
}

Aurelia custom Binding Behavior to observe ALL properties of an object

I would like to create a custom Binding Behavior that allows me to detect ANY changes to the properties of an object, like this:
<my-form model.bind="myObject & objectObserver:myObjChanged()"></my-form>
I know that I can use Aurelia's binding engine to create a property observer, and perhaps I can build this into a custom Binding Behavior to detect the properties of the object and create property observers for each one. But I can't make sense of the binding object that is given to me inside the custom Binding Behavior. Here's my code so far:
import { inject, bindingBehavior, BindingEngine } from 'aurelia-framework';
#bindingBehavior('objectObserver')
#inject(BindingEngine)
export default class ObjectObserverBindingBehavior {
constructor(bindingEngine) {
this.bindingEngine = bindingEngine;
}
bind(binding, scope, interceptor) {
console.warn('hello', binding, scope, interceptor);
}
unbind(binding, scope) {
console.warn('observer.unbind()', binding, scope);
}
}
When the bind happens and the console text is output, I see
So I know it's working, but I don't know what the best object is to start watching. I see the bound object inside targetObserver.currentValue. Is that the best property to start watching? Is there another way that utilizes existing functionality of the Aurelia Binding Engine?
I found a solution that is not Aurelia specific, based on the Proxy functionality built into Javascript.
export function onChangeObj(object, onChange) {
// creates Proxy to detect changes in object properties and call a function
if (typeof onChange !== 'function' || typeof object !== 'object') {
throw new Error('onChangeObj: incorrect parameters');
}
const handler = {
set(obj, prop, value) {
onChange(prop, value);
return Reflect.set(obj, prop, value);
},
};
return new Proxy(object, handler);
}
In order to use it, just call it like this:
this.myObject = onChangeObj(this.myObject, () => this.myObjChanged());
Effectively, the object is replaced by a wrapper Proxy that calls the provided function every time one of the properties is modified (with the setter).
If anyone finds a solution via Aurelia Binding Behavior, I would still be interested.

Facing issue when trying to fake helper function

I am using Nunit and FakeItEasy for my MVC Controller functions.
My Test Code:
[Test]
public async Task Search_Success()
{
if (!isFakeInitialized)
InitializeFake();
url = "/N/UserSvc/v1/Types?skip=0&take=" + Constants.MaxSearchRowNumber;
Types= A.CollectionOfFake<Type>(3);
List<Type> found=new List<Type>(Types);
A.CallTo(() => nFake.GetDataAsync<IEnumerable<Type>>(fakeHttpSession, url)).Returns(Types);
var fakeHelper = A.Fake<helperFunctions>();
A.CallTo(() => FakeHelper.GetAvailableTypes(fakeHttpSession, found, true)).Returns(foundTypes);
//Act
var actionResult = await myController.SearchView();
var viewResult = actionResult as ViewResult;
//Assert
Assert.IsNotNull(viewResult);
Assert.AreEqual("Search", viewResult.ViewName);
}
I am getting error at
A.CallTo(() => nFakeHelper.GetAvailableTypes(fakeHttpSession, found, true)).Returns(foundTypes);
Error: cannot convert lambda expression to type object because it is not a delegate type.
Here is the helper function Code:
public List GetAvailableTypes(Session session,List allTypes,bool includeAllType)
{
List results = new List();
return results;
}
How can i overcome the error.
If nothing else, your A.CallTo should fail because GetAvailableLicenseTypes isn't virtual. I'm a little surprised at the error message, though.
I've tried to reproduce, but had to trim things down quite a bit and fill in missing code, and ended up getting
The current proxy generator can not intercept the specified method for the following reason:
- Non virtual methods can not be intercepted.
Are you able to include more information, starting with the full error, including stack trace?
var nmsFakeHelper = A.Fake<NMCHelperFunctions>();
A.CallTo(() => nmsFakeHelper.GetAvailableLicenseTypes(fakeHttpSession, foundLicense, true)).Returns(foundLicensTypes);
These two lines are your issue.
The first line declares nmsFakeHelper as a fake of concrete type NMCHelperFunctions.
The second line then defines the behaviour of the fake when it's GetAvailableLicenseTypes method is called.
In the background, FakeItEasy decides what type of fake it should use (mock, stub, etc.). If the type you are asking a fake of is concrete you get a stub. However, if you want to be able to define behaviour (define return values or validate that methods were called etc.) you need a mock instead of a stub.
To get FakeItEasy to decide to return a mock instead of a stub, you need to give it an interface type instead. This is because a mock needs to be able to intercept the method calls but in .NET, methods can only be intercepted if they are virtual calls. This happens when the type you are using is an interface, but cannot happen when the type you are using is a concrete type.
So to get around this problem, you should add an interface to the NMCHelperFunctions type that includes (at the very least) the GetAvailableLicenseTypes method (as well as any other methods you may).
This means that your first line will change to the following (assuming you name your interface iNMCHelperFunctions):
var nmsFakeHelper = A.Fake<iNMCHelperFunctions>();
Your second line would remain unchanged, and your test code should now work.
You may have to refactor your application code to use the interface type instead of the concrete type. There is some benefit from doing this because it allows your components to be swappable so it's easier to add or change behaviour in the future by writing a new class that adheres to the same interface and switching to that.

Phalcon Initialize() is not working

I have 2 Controllers, TEST1Controller and TEST2Controller
In TEST2Controller I have a initialize() function setting value of a property.
If I try to access TEST2Controller directly from the browser, everything works perfectly.
But when I call a TEST2Controller method from TEST1Controller, it seems that initialize() function is not being called in TEST2Controller.
TEST1Controller:
namespace Modcont\Controller;
use Modcont\Controller\Test2Controller;
class Test1Controller extends BaseController
{
function gettestAction()
{
$t = new Test2Controller(); // calling TEST2 Controller Method Within TEST1 Controller
echo $t->dotestAction(" MYAPP ");
}
}
TEST2Controller:
namespace Modcont\Controller;
class Test2Controller extends BaseController
{
public $prefix;
function initialize()
{
$this->prefix = 'your name is';
}
function dotestAction($name)
{
return $this->prefix.' : '.$name;
}
}
Phalcon offers two ways for controller initialization, thy are the initialize and onContruct methods. The basic difference between these two methods is that initialize is called only when a controller is created by the framework to proceed with the execution of an action. Since you instantiating a controller object ad-hoc, initialize will not be called, only onConstruct will. So you'll need to put your initialization logic there:
function onConstruct()
{
$this->prefix = 'your name is';
}
Also, implementing native constructors in controller is discouraged, but if you do so, make sure to call the parent constructor in your own constructor: parent::__construct();.
All this information can be found in the Docs.

Yii: attaching events to models

I have a User model which is bundled in a module installed on my Yii application. This module is third party and I do not want to alter its code.
I also have a Cv Model that has a BELONGS_TO relation with the User model.
My question is: How can I delete the cv when a user is deleted ?
I know that I can achieve this with on delete cascade ... on mysql. However, i do need to delete other data such as a photo, files, etc.
What I have tried
I have created a component that is preloaded on my application. This component attaches to an onAfterDelete event
class EventListener extends CComponent
{
public function init() {
Yii::import("application.modules.users.models.User");
User::model()->attachEventHandler('onAfterDelete', array($this, 'deleteUser'));
}
public function deleteUser($event)
{
// stuff here ...
}
}
However this does not work.
Any suggestions ?
This may help you.
User::model() is a singleton
$user1 = User::model();
$user2 = new User; // will be used in insert action
$user3 = User::model()->findByPk(10); // will be used in update/delete action
$user1, $user2 and $user3 are completely different objects.
You can attach events to objects, in this case you have to add events to all these 3 objects individually.
$user1->attachEventHandler(...);
$user2->attachEventHandler(...);
$user3->attachEventHandler(...);
look like Yii does not provide any way to add events at Class level.
Well, guys, I have just stumbled upon the same problem and I solved it this way:
You should use the init() of a Model, not your event listener collection class.
In my case I have devModel class:
public function init()
{
parent::init();
$this->onLicenseUpdated = array(new MEventProcessor, 'licenseUpdateHandler');
}
And the handler is licenseUpdateHandler($event) in a MEventProcessor class.
This way every time you work with model instance, it'll call init() for every object and attach the event handler to every instance of this Model.
Now any time the event (in my case onLicenseUpdated()) is invoked for the model - the handler will be called too.
You could also to use Behaviors.
1 - behaviors can listen to events : you just have to override their events() method
class MyBehavior extends Behavior {
public function events() {
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
public function beforeValidate($event) {
// ...
}
}
2 - although it is more common to attach a behavior to a component in the component's behaviors() method, you can also attach them dynamically and keep the original code unmodified :
use app\components\MyBehavior;
// attach a behavior object
$component->attachBehavior('myBehavior1', new MyBehavior);
You will find some useful documentation here :
yii 1 : http://www.yiiframework.com/doc/api/1.1/CBehavior
yii 2 : http://www.yiiframework.com/doc-2.0/guide-concept-behaviors.html