Laravel Controller -> API class - php

I'm struggling with how to do this correctly following best practices. It might be difficult to explain but i'll try my best here.
I have an external API I need to make very many different calls to. So what I did was creating a class in the App folder called Api.php for now. It's using Guzzle for API calls.
In the Controller for the view I create the Api object in the needed functions and call the corresponding function in the API class.
Controller
public function uploadDevice(Request $request)
{
## Validation etc is performed
// Calling the API
$api = new Api();
$api->uploadDevice();
}
Api.php
class Api
{
private $token;
public function __construct(){}
public function checkIfHasToken(){}
public function getTokenFromSession(){}
public function getFreshToken(){}
public function uploadDevice(){}
}
Some questions
The checkIfHasToken() needs to be called before every request. Should it be done in the constructor, first in each function doing API calls or directly from the Controller?
Exceptions : Where should I do the Try/catch etc ? Should it be done in the Api class where it's needed or in the Controller by calling each and every function from the API class and wrapping it in try/catch?
Redirects : I want to redirect back to the Route the request came from with every possible errors or success message included. So if I have a try/catch I want to redirect with the result of the catch included. Where to put this logic? Redirecting from the nested function does not seem to work. So then I'm back to calling each and every function in the Api class from the Controller one by one and handle the exceptions/errors/validations separately in the Controller?
Maybe I'm thinking too much about this or making it more complicated than it needs to be. Not sure anymore.

// Controller
public function __construct(ApiService $apiService)
{
$this->api = $apiService;
}
public function uploadDevice(Request $request)
{
// Ensure that the user has a token in a custom HTTP request or in a middleware somewhere
try {
$this->api->uploadDevice();
}
catch (Exception $exception){
return redirect()->back();
//You can include errors from $exception here.
}
}
// Service
class ApiService
{
public function uploadDevice()
{
return 'I did a thing';
}
}
Explaination
Laravel has many ways to do the same thing, it is all about what you need and how you want your application to scale.
Checking if a token is present or valid should be done in a middleware.
A try catch can be anywhere depending on how much you need to see in the exception, normally just in a controller is ok, but you can
do this in many ways. I personally like to make an event listener
for any http error.
Return redirect back from the controller will be fine to always redirect to the place that invoked the controller

The checkIfHasToken() needs to be called before every request. Should
it be done in the constructor, first in each function doing API calls
or directly from the Controller?
If it needs to be called for every request, I suggest making it middleware as it's made for this purpose.
Exceptions : Where should I do the Try/catch etc ? Should it be done
in the Api class where it's needed or in the Controller by calling
each and every function from the API class and wrapping it in
try/catch?
This depends, if you want to be able to control the output when an exception occurs then you probably want it in your controller. If you can program something to do when the exception occurs (return unsuccessful for instance), do it in a lower level (api).
Redirects : I want to redirect back to the Route the request came from
with every possible errors or success message included. So if I have a
try/catch I want to redirect with the result of the catch included.
Where to put this logic? Redirecting from the nested function does not
seem to work. So then I'm back to calling each and every function in
the Api class from the Controller one by one and handle the
exceptions/errors/validations separately in the Controller?
You can go back by returning redirect()->back() as the response, the best way to show errors would to include them somewhere. I suggest using session()->flash() for this. These calls can be made from the try/catch.

Related

Execute actions both sync and async

In laravel 5.7 I want to be able to have multiple actions,
for example inserting user in database, sending registration Email,
sending notification, ...
I want to be able to execute these actions both sync and async.
the problem is I don't want to create Job class for every action.
each action is a php callable class.
The thing I don't understand in Laravel Job class is It receives dependencies as handle method arguments and receive It's Input which should process on,in the constructor, I think It's kind of odd.
for example when I want to call send register email action, I want to be able to do sth like :
$registerEmailAction->__invoke($user, true);
second parameter indicates whether to do this action sync or async.
Laravel Job classes are simple objects that only need to implement the ShouldQueue interface and an handle() method. You can dispatch them, or run them immediately explicitly calling the handle method. If you want to take the __invoke route you could so something like this:
class RegisterEmailAction implements ShouldQueue
{
//... more code ...
public function __invoke(User $user, bool $async)
{
$this->setUser($user);
if ($async) {
dispatch($this);
}
else {
$this->handle(); // or dispatch_now($this);
}
}
public function handle()
{
if (!$this->user) {
throw new UserNotFoundException();
}
// ... some other code ...
}
}
Since you don't want to pass the $user as a dependency in the constructor, I would suggest to check for it in the handle method, so that you get an error if some client code tries to call the handle method, without taking the __invoke route. You also may needs to use some traits, like SerializeeModels or Dispatchable (check the docs for more info).

Commands return data

I'm trying to clean up some of my controllers by moving the functionality from the controller methods to reusable commands that I can just pass on to a command bus. As I've understood, though, a command is not supposed to return data (or did I misunderstand something?). But how else would you suggest for the controller to act on the outcome of the command?
For instance I have a Create endpoint in my ClientController. The controller method dispatches a CreateClient command, and then I'd like to redirect the user to the new Client's edit page. Something like
class ClientController extends Controller
{
public function create($request)
{
try {
$this->dispatch(new CreateClient(/*get something from the request to pass*/));
} catch (\Exception $e) {
return $this->json(['error' => $e->getMessage()]);
}
$client = ...;
$this->redirect('/client/' . $client->id);
}
}
If the creation failed I can throw an exception, which can be handled by the controller. I could of course create a method in my ClientRepository to get the newest created Client, but that seems error prone. But how would you suggest that I got a hold of the newly created client?
Edit:
At the end of https://www.youtube.com/watch?v=fbSYZFZCFS0 they discus this actual problem. Their main suggestions are using UUIDs so the ID can be generated by the controller, or using an asynchronous method to poll for the new item.
They also mention the possibility of using event listeners, but argue that it's an anti-pattern in PHP.
I'm still undecided on how I'll prefer to do it.

Call exit function from child class in PHP

I have a PHP class where I have to invoke PHP header function to show a webpage. As pointed out from this post, header should be followed by exit callback. Since this situation is very common in my classes, I've defined a method in parent class:
class ParentClass
{
protected function header($url)
{
header('Location:'.$url);
exit;
}
}
I would like to invoke this method from children classes:
class ChildClass extends ParentClass
{
public function someFunc()
{
$this->header($some_url);
}
}
PHP documentation says that exit terminates the current script. My question is: does the exit function terminate child script even if it is contained in parent class?
EDIT
In my specific situation, I am using a MVC design pattern and ChildClass is a controller. Inside it, sometimes I need to show a view, sometimes I need to redirect to another url. Let me explain it with a practical example.
Suppose to have a website with a login section. When login page is displayed to users (login data not submitted), login controller should show login view. This view contains a form with an action like action="login.html". When data is submitted, login controller is invoked and checks login data: if login is successful, user is redirected to his admin section.
class UsersController extends BaseController
{
public function login()
{
try
{
if(isset($_POST['submit']))
{
// check login data, throw exception in case of error
// if success, redirect to admin section
$this->header('admin.html');
}
else
{
// show login view
}
}
catch(Exception $e)
{
// show login view (with error)
}
}
}
class BaseController
{
protected function header($url)
{
header('Location:'.$url);
exit;
}
}
Since this situation is quite common in my controllers, I've preferred to define a header method in BaseController instead of typing everytime
header('Location:someURL.html');
exit;
In my OP, I only wanted to be sure that $this->header('admin.html'); callback would terminate current login method, even if it is defined in BaseController script.
Hope it's a little clearer now.
As already descripted in the comment, exit will terminate everything, i.e. the webpage immediately stops executing, including clean up functions and finally blocks.
So, you should consider using exit very carefully because many things might happen: data doesn't get written to the database when you're not using auto-commit (unless you commit the data before calling exit). Auto-commit is not enabled by default in PHP's MySQL module (as far as I know).
Here is an example:
class B extends A {
public function someFunc() {
# you might wanna use partent instead as
# pointed out by Ding in the comments, but
# maybe someFunc does more that just doing
# the redirect.
$this->header("http://google.com");
}
}
try {
print("Ok...");
$obj = new B();
$obj->someFunc();
print("Nahh..."); # doesn't get called/
} finally {
print("Cleaning up."); # doesn't get called, either.
}
Instead of calling the exit method, you should rather implement a clear MVC design pattern. Here is a very quick example:
<?php
class Response {
# use private here and use appropriate
# getters and setters.
public $status_code = 200;
public $content = "";
public $headers = array();
}
class HomeView extends View {
# called when a get request is made.
public function get() {
$response = new Response();
$response->content = "Hello world."
}
}
class RedirectionView {
public function get() {
$response = new Response();
$response->status_code = 301; # use 302 if moved only temporarily.
$response->headers["Location"] = "http://google.com";
}
}
function processRequest() {
# load appropriate view programatically
$view = new RedirectionView();
$response = $view->get();
http_response_code($response->status_code);
foreach ($response->headers as $headerName => $headerValue) {
header(sprintf("%s: %s", $headerName, $headerValue));
}
print($view->content)
}
?>
Note that this is not really a MVC design pattern (the model is missing and, well, it's not clear what the controller, however, that's what django (a Pyhton framework) uses, too). You might wanna check out PHP MVC frameworks (a quick google search will do the trick) or implement your own (my example might be a good start).
Edit 1:
Using exit is probably ok (but I wouldn't use it because I believe it is bad practice). If you're able to edit the design approach you're using, I'd make your View BaseClass get/post method (or whatever you're the method that returns response object. Note: if you're using prints to show the response to the user, try to add a Response which contains all the data you're request needs to show. This is better than having prints (or echos) everywhere in your code).
Using the response object, your code would then be able to either just set the location header (if it's a redirect) or show the message. So you don't need to "kill" any part of the webpage and it will terminal when the execution ends. But again: you're probably good to go with an exit call in your method (unless you're encountering any issues with your code (database transactions that aren't committed, statistic data that is not updated (because its after the exit statement). Exit will simply terminate your script completely.

Calling other Controller Methods in laravel

I'm relatively new to the laravel framework and i noticed a pretty disturbing issue with laravel controllers. I dont know if its me but it doesnt seem to work at all. Lets say i have a controller and i want to split the logic contained in the method called when the request hits a route tied to the controller.
class SomeController extends BaseController
{
function doSomething()
{
$this->doSomethingExtra();
}
function doSomethingExtra()
{
return "Something Extra Done";
}
}
And lets say a have to route defined like so
Route::get('main/dashboard','SomeController#doSomething');
the second method called from the first never returns the string "Something Extra Done" to the browser. infact it returns an empty 200 response. However this seems to work when you return response from the doSomething() as usual. Anyone know why this is behaving this way? is there anyway to breakup a controller logic into several Methods that actually return responses to the client?
Change this
$this->doSomethingExtra();
to this.
return $this->doSomethingExtra();

Can a site user pass their own arguments to model functions?

Are functions inside of models directly accessible by users?
Can a user pass arguments directly to a function in a model? Or, do arguments have to be passed through php?
In otherwords:
I have a model called notifications and in there a function called get_notifs($user)... I use the controller to call the function like the get_notifs($_SESSION['user_id']) (which is encrypted). I don't want someone to be able to call get_notifs() with anything but their $_session as a argument. What is the best solution?
Am I already okay?
Should I rename get_notifs() to
_get_notifs()?
Should I check the
$_SESSION['user_id'] in the method
itself?
Or, is there another better solution
than any of these?
I have a controller: ajax.php which loads the model notification
function __construct()
{
parent::__construct();
$this->load->helper('url');
$this->load->library('tank_auth');
$this->load->model('notification');
$this->load->model('search');
}
function get_notifs()
{
$me = $this->session->userdata('user_id');
if ($e = $this->notification->get_notif($me))
{
...........
}
else{
echo "nothing was found wtf?";
}
.........................................................
model: notification.php
function get_notifs($user){
......
}
Your code is perfectly fine!
Am I already okay?
I Think so
Should I rename get_notifs() to _get_notifs()?
No, it's a public method so no need to make it look private.
Should I check the $_SESSION['user_id'] in the method itself?
No, this is the controller's job
Or, is there another better solution than any of these?
You only need a solution to a problem, and i don't see a problem here
it sounds liek your application may be used by people other then yourself, i.e the public developers, why would you want enforce developers to code things your way, that's going to make them upset at your application.
CI Only routes requests to a controller, the user cannot access a model or library or any other class, the route goes like so: /controller/method/param
the first segment will only ever load a controller file, the second will call the method in the param, passing any other variables such as param to that method.
Source: http://codeigniter.com/user_guide/overview/appflow.html
As you can see from the flow chart above, only the controller has access to the model's
If you'll only use it while in a session the best way would be this:
function get_notifs(){
if(!isset($_SESSION['user_id'])){
return false;
}
$user = $_SESSION['user_id'];
/* Your code here */
}
There's no point of requiring an argument when you'll only use the function with one specific variable which is also available globaly.
Edit: I don't know why you're using functions in your models. Doesn't make any sense, do you mean methods?

Categories