How does this callback works for a cakePHP component? - php

In CakeDC comments plugin for CakePHP documentation states that:
Component callbacks
It is possible to override or extend the most comments component
methods in the controller. To do this we need to create method with
prefix callback_comments Examples:
callback_add will named as callback_commentsAdd in controller,
callback_fetchData will named as callback_commentsFetchData in
controller. ...
It works from the controller perfectly!:
public function callback_commentsInitType() {
return 'flat'; // threaded, tree and flat supported
}
I wonder, what is the new feature of cakephp-2.0 that allows you to do that? I need to understand how it was achieved to be able to implement such methodology in the future on my components.

In the code of the component, if you look at the following function in this file (starting from line number 622):
/**
* Call action from commponent or overriden action from controller.
*
* #param string $method
* #param array $args
* #return mixed
*/
protected function _call($method, $args = array()) {
$methodName = 'callback_comments' . Inflector::camelize(Inflector::underscore($method));
$localMethodName = 'callback_' . $method;
if (method_exists($this->Controller, $methodName)) {
return call_user_func_array(array(&$this->Controller, $methodName), $args);
} elseif (method_exists($this, $localMethodName)) {
return call_user_func_array(array(&$this, $localMethodName), $args);
} else {
throw new BadMethodCallException();
}
}
You can see that the variable $methodName is being defined with prefix callback_comments and then the passed $method is appended to it after being treated by the Inflector::underscore and then Inflector::camelize method. The working of these is as follows:
Inflector::underscore will convert initType to init_type. Check doc here.
Inflector::camelize will further convert init_type to InitType. Check doc here.
Now, if initType was passed in the argument, then the $methodName will be:
callback_comments + InitType = callback_commentsInitType
After this, a $localMethodName is also being generated. In our initType example, it will be:
callback_ + initType = callback_initType
After the names have been generated, it will simply search if the method exists in the attached controller and will execute it using call_user_func_array function by passing it and array with the object (in our case, the controller object (&$this->Controller) or the component object itself (&$this)) containing the method and the $methodName as first argument and then $args as second argument.
If the function was not found in the controller, then it will instead search in the component with $localMethodName. If it's found, then it is executed the same way.
Now how all this works is that the _call function is the single function used to call all the internal functions of the component, so that it will first check if the function has been overwritten in the controller, otherwise it will execute the function in the component itself.
You can check the component's beforeRender function here and you'll see how the initType function is called. In this case, if the controller contains a function named callback_commentsInitType, then it will be executed. Otherwise, the components callback_initType will be executed.
Hope this helps..

Related

how does laravel with() method work for RedirectResponse

really this question makes me crazy. now I want to ask about laravel with() which is used for RedirectResponse.i searched a lot and research but I am not able to get success and proper satisfaction.so here is the main question.
we can use with() for RedirectResponse like this
return view('name')->with(['demo'=>$demo]);
Or
return view('name')->with('demo',$demo);
given above methods are understandable for me
the problem is here.we can also use like this.
return view('name')->withDemo($demo);
so i am not able to understand logic behind withDemo() because withDemo() is not defined any where.i searched in laravel about with(). i found some info about that here is the path
vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php
I found something like this
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if (Str::startsWith($method, 'with')) {
return $this->with(Str::snake(substr($method, 4)), $parameters[0]);
}
throw new BadMethodCallException(
"Method [$method] does not exist on Redirect."
);
}
please anyone can explain the logic behind dynamic defined method like withDemo()
it could be duplicate question sorry for that but I searched a lot. I did not find related to this topic.
The same sort of magic does appear on the RedirectResponse, but you're probably interested in this code that on the Illuminate\View\View:
/**
* Dynamically bind parameters to the view.
*
* #param string $method
* #param array $parameters
* #return \Illuminate\View\View
*
* #throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if (! Str::startsWith($method, 'with')) {
throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}
return $this->with(Str::camel(substr($method, 4)), $parameters[0]);
}
Like #abrar pointed out this is using PHP's magic methods. When you call a method that does not exist on an object PHP will instead call __call() passing in the method name and the arguments.
You can see here that Laravel will first check to see if a macro exists and use that if it does. Otherwise it confirms that the method name starts with with and then simply passes it on to the with() method as it would if you called it directly.
__call($method, $parameters) gets called whenever you try to call an undefined method.
When you call withDemo($demo) the interpreter jumps into __call($method, $parameters)
$method = 'withDemo';
$parameters = [$demo];
'with' gets stripped in the substr function call and it get's snake cased.
The 'real' call with variable names 'stripped' looks something like this:
return $this->with('demo', $parameters[0]); // $parameters[0] -> $demo
This is easier way to write function and parameter together
eg. with('key1', 'value1') OR withKey1('value1')
eg. where('mycolumn', 'value') OR whereMycolumn('value')

How can I mock a method that should accept an array?

I am new to testing with PHPUnit, and what I am trying to do is test a method called returnOnLogin() that accepts a parameter Enlight_Event_EventArgs $args and returns true.
Here's the method I want to test:
public function returnOnLogin(\Enlight_Event_EventArgs $args)
{
$controller = $args->get('subject');
$view = $controller->View();
$controller->redirect([
'controller' => 'verification'
]);
// $view->addTemplateDir(
// __DIR__ . '/Views'
// );
return true;
}
Here's my test:
class MyFirstTestPluginTest extends TestCase
{
public function testReturnOnLogin()
{
$my_plugin = new MyFirstTestPlugin(true);
$expected = true;
//I tried following but it did not work
$this->assertEquals($expected, $my_plugin->returnOnLogin(//here is the problem it requires this array that I dont know));
}
}
Assuming that your controller class is Controller, and assuming that we don't care that view() is invoked in $controller, this should cover what you are looking for:
class MyFirstTestPluginTest extends TestCase
{
public function testReturnOnLogin()
{
/**
* create a test double for the controller (adjust to your controller class)
*/
$controller = $this->createMock(Controller::class);
/**
* expect that a method redirect() is called with specific arguments
*/
$controller
->expects($this->once())
->method('redirect')
->with($this->identicalTo([
'controller' => 'verification'
]));
/**
* create a test double for the arguments passed to returnLogin()
*/
$args = $this->createMock(\Enlight_Event_EventArgs::class);
/**
* expect that a method subject() is invoked and return the controller from it
*/
$args
->expects($this->once())
->method('subject')
->willReturn($controller);
$plugin = new MyFirstTestPlugin(true);
$this->assertTrue($plugin->returnOnLogin($args));
}
}
What does this test do?
Arrange
This test first arranges test doubles for use with the system under test (your plugin).
The first test double is your controller, we set it up in such a way that we expect that a method redirect() is invoked once with an argument that is identical to the specified array.
The second test double is the argument, we set it up in such a way that we expect that a method 'subject()` is invoked one, and will return the controller.
Then, we set up the system under test, simply by creating an instance of MyFirstTestPlugin, passing true to the constructor.
Unfortunately, you haven't shared the constructor with us, we have no idea what the argument true stands for. If it affects the behaviour of returnLogin(), then we clearly need to add more tests to assert the behaviour when the argument takes different values.
Act
Then this test invokes the method returnLogin() on the system under test, and passes in one of the test doubles.
Assert
Eventually, this test asserts that the method returnLogin() returns true.
Note Take a look at
http://wiki.c2.com/?ArrangeActAssert
https://phpunit.de/manual/current/en/test-doubles.html

Laravel - Type error: Too few arguments?

I get an error:
Type error: Too few arguments
I thought Laravel do some magic to if arguments is not fully passed?
For example:
In the Controller I have:
public function create(CreateRequest $request)
{
return $this->todoService->createList($request);
}
In the todoService class:
use App\Plan
class todoService {
public function createList($request, Plan $plan)
{
//
}
}
As you can see I did not pass Plan class object. Do I have to bind or something?
If you are calling createList() by yourself so you will need to pass both parameters by yourself. You can bind something with Plan but still if you will call something not Laravel, then you will be responsible to pass that function parameters.
This type hinting only works if Laravel is calling that function. So you need to call these functions through Laravel.
If you are trying to automatically injecting in a class's constructor, you can simply do this:
$service = $this->app->make('App\Plan\todoservice');
or
$service = App::make('App\Plan\todoservice');
or
$service = resolve('App\Plan\todoservice');
But this will only work for Constructors. Please note that you can also provide parameters as next arguments of make() or resolve() function.
In fact, different methods can also be called that way.
You can simply do:
$service = App::make('App\Plan\todoservice');
$container->call([$service, 'createList'], ['request' => $request] );
Here $container is object of Illuminate\Contracts\Container\Container.
You have to bind classes only if they depend on interfaces. If you specify particular class, reflection will do the job for you. documentation
The only way this will work, is to set the default value of second parameter. In any other situation, syntax exception will be thrown.
use App\Plan
class todoService
{
public function createList($request, Plan $plan = null)
{
//
}
}
public function create(CreateRequest $request)
{
return $this->todoService->createList($request);
}
It will work, but will that make any sense?
Laravel cannot do any magic on this level as your coding error is simply a PHP syntax error. You're indicating that the second parameter is of type Plan, which makes it mandatory implicitly. Laravel cannot 'patch' simple function calls like this.
Your confusion is likely in that, depending on your routing, Laravel can inject the correct Plan parameter into the create controller method, thus allowing you to forward it into the service.
/**
* Create a new personal access token for the user.
*
* #param string $name
* #param array $scopes
* #return \Laravel\Passport\PersonalAccessTokenResult
*/
public function createToken($name, array $scopes = [])
{
return Container::getInstance()->make(PersonalAccessTokenFactory::class)->make(
$this->getKey(), $name, $scopes
);
}
/**
* Set the current access token for the user.
*
* #param \Laravel\Passport\Token $accessToken
* #return $this

Writing specs for a class that behaves differently depending upon constructor arguments

If you have a class that responds differently depending upon constructor arguments, how do you go about writing a spec for that class?
class Route
{
function __construct($url, array $methods = array())
{
// stores methods and url in private member variables
// creates a regex to match $url against incoming request URLs
}
public function isMatch($url)
{
// checks if the incoming request url matches against this url
}
}
Example use:
$a = new Route('/users/:id');
$a->isMatch('/users/1') // returns true;
$b = new Route('/users');
$b->isMatch('/users') // returns true
If I set up my spec for this class using the let function from phpspec:
class Route extends ObjectBehaviour
{
function let()
{
$this->beConstructedWith('/users/:id')
}
}
My spec can only check if the behaviour of this class works in one of the cases.
I've contemplated adding setter methods to allow me to test around this, but it seems like I'd be breaking encapsulation for the purpose of testing.
I'm struggling to find anything that touches upon this, so I'm started to think that maybe this is bad code smell situation.
beConstructedWith() doesn't always need to be called from the let() method. You can call it from the specs as well.
In my opinion there's nothing wrong in setting up an object in more than one way. However, you should avoid doing too much work in the constructor.
Constructor should be used only to obtain variables that will be set to a member properties here. No further logic should be done here...
Following the idea from point 1 there should be another logic that determines what happens next (e.g. if Object->hasProperty(X) then do x(), etc.)
Then a comment would be plain and straight forward.
Example:
class Route
{
private $url;
private $methods = array();
/**
* Constructor method, sets the attributes to private member variables
* #param string $url URL pattern
* #param array $methods Methods that should be used with given URL
*/
function __construct($url, $methods = array())
{
$this->url = $url;
$this->methods = $methods;
}
// ...
}

getStaticHelper(action helper) getting parameters but giving error

I know how to pass parameters to a normal action helper that's called in the action itself. But this time I'm doing it in the Bootstrap using HelperBroker::getStaticHelper
$hooks = Zend_Controller_Action_HelperBroker::getStaticHelper('Test');
Zend_Controller_Action_HelperBroker::addHelper($hooks);
I want to pass a parameter so I added the middle line
$hooks = Zend_Controller_Action_HelperBroker::getStaticHelper('Test');
$hooks->preDispatch($input);
Zend_Controller_Action_HelperBroker::addHelper($hooks);
and the preDispatch is this
public function preDispatch($input){
var_dump($input);
}
The strange thing is that var_dump shows me the input, but I also get this error
Warning: Missing argument 1 for Test::preDispatch(),
Notice: Undefined variable: input
preDispatch() is a hook called in dispatch loop. You shouldn't use it like this.
Zend_Controller_Action:
/**
* Dispatch the requested action
*
* #param string $action Method name of action
* #return void
*/
public function dispatch($action)
{
// Notify helpers of action preDispatch state
$this->_helper->notifyPreDispatch();
...
$this->_helper->notifyPostDispatch();
}
Also this code is ambiguous:
Zend_Controller_Action_HelperBroker::addHelper($hooks);
Action helper was registered within helper broker by getStaticHelper() method call
You should make it like this:
class MyHelper extends Zend_Controller_Action_Helper
{
const BAR = false;
public function preDispatch($request)
{
$this->ifBarExit(self::BAR);
}
public function ifBarExit($barValue)
{
if ($barValue) {
exit('Bar was true!');
}
}
}
The preDispatch thinks it's first variable is request object - it's how it'S wired in the ZF's internals.
But now with the new function you can do:
$helper = Zend_Controller_Action_HelperBroker::getStaticHelper('MyHelper');
$variable = true;
$helper->ifBarExit($variable); //won't exit
You shouldn't mess with the internal methods (i.e. call them) for your own vilain plans. If you want to inject something in the helper, don't pass it directly. Add member variable and sth like $helper->setImportantThing($thing); which will save it into protected $_thing; and then inside the method echo $this->_thing;

Categories