cakePHP duplicating a function or using an outer controller? (DRY issue) - php

I have 3 controllers, Tokens, Stores and Users.
Token is related to the two other models, for each token there is a owner-type and owner-id.
There is also a function in both User_controller and Store_controller, called EmailTokenToUser which send the activation link to the person that registered a user or a store.
My question is: should i pass the function to the token?
if so, how should i call it? (requestAction is a bad idea, creating an object just for one function..)
any ideas?

To reuse code you should pass it to token's MODEL not the controller. That is the way it should be not the only way. To use it you should NOT use requestAction even if is inside the controller (again it is the way it should be you CAN do it with requestAction). You can do this in two ways.
App:import('model', Token');
Token::myFunction($args);
OR
$token = ClassRegistry:init('Token');
$token->myFunction($args);
OR (if you are colling it from inside a controller you may use also)
$this->loadModel('Token');
$this->Token->myFunction($args);
This is if you put it in the Token model the function. If not and go with the controller way you should do it like this
App:import('controller', 'Tokens');
TokensController::myFunction($args);
OR
App:import('controller', 'Tokens');
$token = new TokensController();
$token->myFunction($args);
Hope it helps you :)

Related

Laravel testing. Is possible to use multiple calls in same test?

I have this test:
public function test_user_can_access_the_application_page(
{
$user=[
'email'=>'user#user.com',
'password'=>'user1234',
];
$response=$this->call('POST','/login',$user);
$this->assertAuthenticated();
$response->assertStatus(302)
->assertRedirect('/dashboard')
->assertLocation('/dashboard');
$response=$this->call('GET','/application/index');
$response->assertLocation('/application/index');
}
After I log in, it directs me to the dashboard ok until now, but if I want to access the other page after that, I cant. This error comes up.
Expected :'http://mock.test/application/index'
Actual :'http://mock.test'
Aren't multiple calls allowed in the same test, or is another way to access other pages after login?
(Note: It's not possible to use factories for the actingAs so I need to login).
If you can't use factories for actingAs, then you should try with cookie.
Look at the https://github.com/firebase/php-jwt library.
I guess you will need to call the function as an user, since you can only access it logged in. Laravel provides the actingAs() method for such cases.
https://laravel.com/docs/7.x/http-tests#session-and-authentication
You can create a random User who has the permission to log into your app or take a seeded one and call the function acting as the chosen User.
$response=$this->actingAs($user)->call('GET','/application/index');
If you call it without actingAs(), your middleware will redirect you back to the login or home screen (what you defined in the LoginController ).
In my opinion this test case should have its own testing method. I recommend using a test method per route or per use case. It makes your tests clearly arranged and easy to understand.
If you want to be authenticated, the easiest way is to have PHPUnit simulate authentication using the actingAs() method.
This method makes the user authenticated, so you wouldn't want to test the login method with it. You should write your login tests separate from testing the other pages.
To answer your question, yes you can make multiple requests in the same test, but in this case linking the login test to the 'application/index' page likely does not make much sense.
public function test_the_user_can_login()
{
$user = [
'email'=>'user#user.com',
'password'=>'user1234',
];
$response = $this->call('POST','/login',$user);
$this->assertAuthenticated();
$response->assertStatus(302)
->assertRedirect('/dashboard')
->assertLocation('/dashboard');
}
public function test_user_can_access_the_application_page()
{
$user = User::where($email, "user#user.com")->first();
$response = $this->actingAs($user)
->call('GET','/application/index');
$response->assertLocation('/application/index');
}
I experienced that in laravel 8 I use comment #test and involved second test!!! I mean if you use two function for test you must us #test that php artisan test, test both of them.

PHP how to pass array from view to controller and then to a different view from controller?

I have been trying to pass an array that is generated in view to controller.
Say, $abc=array(); How do I sent this array to controller, then process it and sent this array to another view?
Is it even feasible? Not getting any answer after googling! Please help.
I see no point to generate array from inside the view.
It depends how your framework passes the data between the layers.
For example the controller do pass the data to the View object.
I.e.
public function someMethodInController() {
$this->view->varName['arr_key'] = 'something';
}
But talking about view, it might not have a certain controller already instantiated (usually it's against the MVC paradigm)
So I see two major ways:
Instantiate a controller (I would not prefer this)
$controller = MyController();
$controller->abc = $abc;
Or use sessions. This way to transmit data is universal inbetween your domain, and does not care if you are using a certain pattern, or how do you transmit between layers.
$_SESSION['abc'] = $abc;
Once assigned, you can use it in the same session from each of your files.

Cannot expire cookie from a controller in CakePHP 2.x

I am building an API in CakePHP. I have a function that as part of its execution first destroys the cookies associated with the session. I am using the following code to do this.
public function new_thing () {
// I first call another controller to use functions from that controller
App::import('Controller', 'Person');
$PersonsController = new PersonsController;
// This function call is the problem
// This does not throw any errors but does not destroy the cookie as requested
$PersonsController->_kill_auth_cookie()
}
// This is from the Person controller, these are the functions used in the API
// This is the function that sets the cookies
public function _set_auth_cookie( $email ) {
setcookie(Configure::read('auth_cookie_name'), $email);
}
// this is the function that does not properly destroy the cookie from the API
// interestingly, it does work when called from in this controller
public function _kill_auth_cookie() {
setcookie(Configure::read('auth_cookie_name'), 'xxx', time()-7200);
}
I cannot get the API to properly expire the cookie that is created earlier in the session, I am not sure why. Additionally—what is maddening—is that the logs are empty and no error is being thrown of any kind, so I am not sure what to do next.
There is so much wrong in this code and concept…
DON'T instantiate controllers anywhere. It is plain wrong, broken by design and violates the MVC pattern. Only one controller should be dispatched by the framework itself based on the request; you don’t instantiate them manually.
An API using cookies? Well, not impossible but definitely not nice to work with. It’s possible but I’ve never seen one in the wild. I feel sorry for the person who has to implement it. See this question.
Why are you not using the CookieComponent? It has a built-in destroy() method to remove a cookie.
If you have an “auth” cookie, why are you not using CakePHP’s built-in Auth system? It will deal with all of that.
Use App::uses() not App::import() here
By convention, only protected functions should be prefixed with _
The first point is very likely the reason why cookie and sessions are messed up because the second controller instance initiates components again, and by this cookie and session maybe a second time as well. However, this can lead to “interesting” side effects.
I first call another controller to use functions from that controller
This is the evidence that your architecture is broken by design. The code that needs to be executed somewhere else; should be in a model method in this case. Or at least a component if there are controller-related things to be shared between different controllers.

MVC: what code belongs to the model

I've started development on a CakePHP project since a few weeks now. Since the beginning I was struggling with the amount of code inside the controllers. The controllers have, in most cases more lines of code than the models. By knowing the expression "Skinny controller, fat model" I'm searching for some days now for a way to put more code in the models.
The question arises at this point is, "where to draw the line". What should the controller do and what should the model do. There are already some questions/answers on this only I'm searching for a more practical explanation. For example I've put a function below which is now inside the controller. I think a part of this code must and can be moved to the model. So my question is: what part can I move to the model and what can remain in the controller.
/**
* Save the newly added contacts and family members.
*/
public function complete_contacts()
{
if ($this->request->is('post')) {
if (isset($this->data['FamilyMembers'])) {
$selected_user = $this->Session->read('selected_user');
$family_members = $this->data['FamilyMembers'];
$this->ContactsConnection->create();
foreach ($family_members as $family_member) {
// connection from current user to new user
$family_member['ContactsConnection']['contact_family_member_id'] = $selected_user['id'];
$family_member['ContactsConnection']['nickname'] = $selected_user['first_name'];
$this->ContactsConnection->saveAll($family_member);
// inverted connection from new user to current user
$inverted_connection['ContactsConnection']['family_member_id'] = $selected_user['id'];
$inverted_connection['ContactsConnection']['contact_family_member_id'] = $this->FamilyMember->inserted_id;
$inverted_connection['ContactsConnection']['nickname'] = $family_member['FamilyMember']['nickname'];
$this->ContactsConnection->saveAll($inverted_connection);
}
}
}
}
Should I create a function in the FamilyMember model called: "save_new_family_member($family_member, $selected_user)"?
As far as the purposes of the M and the C
The model manages the behavior and data of the application domain,
responds to requests for information about its state (usually from the
view), and responds to instructions to change state (usually from the
controller).
The controller receives user input and initiates a response by making
calls on model objects. A controller accepts input from the user and
instructs the model and a view port to perform actions based on that
input.
I would suggest you can pass
$selected_user = $this->Session->read('selected_user');
To your Model and perform your for each inside of your Model. You may want to change rules as to how the data is stored or perform some transformations on it and the Controller should be blind to this. Basically use the Controller to get your information [from the View often] to the Model. Don't directly manipulate the Model from the Controller. In short YES create the function that you suggested :)
That being said sometimes I find myself in a position where my Controller has to do more than I'd like, in which case at least break the task down into helper methods that way your controller is more manageable and you can reuse code where needed.
You are doing it right.
You can of course create some methods in model and make it fat with:
function updateContactFamilyMemberId($id){}
function updateNickname($nickname){}
...
In my opinion it still will be correct, but unnecessary.

Admin panel - what is the best way to display "static" data in the layout?

I'm about to write a admin panel for my CMS written in CodeIgniter. There will be some user information visible at all time - both in the layout's header section and the sidebar. I'm used to do it in a way that I personally hope and think could be done a lot easier, since I'm tired of sending the same parametres to the view over and over again, when it's dynamic data that needs to be displayed on every page anyways (such as unread messages, username, name, status, etc).
I'll need controllers and models, I know that, but do I have to pass, just for an example, the user's username, unread messages etc. every time I need to load a view? Should I do some kind of library for this?
Now my question is: How would I do it when it comes to best practice and for making it easy to maintain in the future?
I hope my question is understandable :)
Personally, I would extend the Controller library (create a MY_Controller by following the guidance at the bottom of Creating Libraries at codeigniter.com).
You would use your model etc as normal. Then you would create a private function in your MY_Controller class to get the relevant "global" data and call
$this->load->vars('everywhere_data', $data_from_relevant_models);
which would make the data available to all views called from that point on as $everywhere_data. Then add a reference to that function in the constructor of MY_Controller, perhaps with a conditional checking for the user to be actually logged in.
If it's complex to collect and get all that data, you might write a library to handle it for you, but the 'controller' part would still be done by MY_Controller: i.e. to get the data and then use load->vars() to publish it to the view.
As a quick and untested example, MY_Controller would start something like as follows:
<?php
class MY_Controller extends Controller
{
private $logged_in_user;
function MY_Controller()
{
parent::Controller();
if( $this->_logged_in_userid() > 0 )
{
$this->logged_in_user = $this->_get_user( $this->logged_in_userid() );
$this->load->vars('logged_in_username', $this->logged_in_user->username );
} else {
$this->logged_in_user = false;
}
}
...
}
Note that things like _logged_in_userid() would access the session for you (e.g. return $this->session->userdata('logged_in_userid');), and _get_user() would access the relevant models.
Finally, you would have a view that accesses $logged_in_username (or everywhere_data in my first example) which you would call into your headers etc. This leaves your normal controllers uncluttered so that they can focus on delivering their specific functionality, stops you rewriting your code several times AND maintains the MVC ideals.
You could create a View just to hold the information and get it from a $_SESSION variable in the View itself if you want to keep it all in one place.

Categories