I Need some advice, as I'm still a bit new to Laravel and MVC in general. I'm coding a small web application that presents some data on the page, fetched from a remote API. However, the page already has a controller to it. The other controller I will be using I'm hoping I can also reuse it for other pages. I'm pretty stuck here.
So the two controllers
HomeController.php
ApiController.php
The HomeController is the original controller, which gets the view file (home.blade.php), with some other data that's being loaded from the controller.
With the ApiController, I want to fetch the api (json) results, do some changes and then load those changes to the HomeController as well. The changes would be like an array of methods and such that's being loaded to the view.
So How can I load both controllers inside of the same view?
First of all controllers doesn't get loaded inside view instead, you should load a view from a controller and to make the remote request for an API call you don't need to use another controller but you may use it if you have other use of API and need a separate controller. The flow is something like this:
class HomeController extends BaseController {
public function index()
{
// make the api call/remote request
// modify the returned data
// load the view
}
}
Let's rewrite it:
class HomeController extends BaseController {
protected $apiService = null;
public function __construct(ApiService $apiService)
{
$this->apiService = $apiService;
}
public function index()
{
// make the api call/remote request
$apiData = $this->apiService->makeRequest();
// modify the returned data.... then...
// load the view
return View::make(...)->with('apiData', $apiData);
}
}
So, it seems clear that, you should use the API related process in a separate class as a service, maybe a model or a simple repository class and inject it to your HomeController then use it from the controller.
Do all the API stuffs in ApiService and call methods of that class from the HomeController, in this case you may implement the ApiServiceRepository as a concrete class by implementing an interface, i.e. ApiService. So, finally it could be like this:
interface ApiService {
public function makeRequest();
}
// Implement the interface in concrete class
class ApiServiceRepository implements ApiService {
public function makeRequest()
{
// $data = make remote request
// return $data
}
}
Use the class HomeController with __construct as given above and add a IoC binding like:
App::bind('ApiService', 'ApiServiceRepository');
So, you don't have to worry about the dependency injection in the constructor of your HomeController.
BTW, to use a method from another controller, for example ApiController from HomeController you may use something like this:
$apiController = App::make('ApiController');
// Call any method of "ApiController" class/object
$apidata = $apiController->makeCallToMethod();
You may also check this article for understanding the use of repository pattern in Laravel.
Related
So I'm really trying to figure it out how can I do that in Symfony 5.
I have an services named PaymentRequestService which have the entire logic for requests to another application (based on microservices).
I injected PaymentRequestService in PaymentService as constructor, an service which processes data, make validation, etc.
And right now I'm trying to call from my controller an method from PaymentRequestService by using PaymentService. Something like that: $paymentService->$paymentRequestService->method.
Can someone tell me how can I do that?
Right now it looks something like that $payment = $paymentRequestService->getPaymentRequest((string)$id)
But I want to eliminate PaymentRequestService.
I dont argue the architecture you want to use... but you would do it this way:
(PHP 8 syntax)
class PaymentService
public function __construct(private PaymentRequestService $requestService)
{}
public function getRequest(): PaymentRequestService
{
return $this->requestService;
}
}
class MyController extends AbstractController
{
public function myAction(PaymentService $paymentService, Request $request): Response
{
$id = $request->get('id');
$payment = $paymentService->getRequest()->getPaymentRequest((string)$id);
return new Response('done');
}
}
you also could map specific methods to dont need the cascading - but then why you dont use the RequestService directly in the first place.
That should be doable over a getter like you have implemented currently, but why not just pass the PaymentRequestService to the controller where it is used?
e.g. $paymentRequestService->method()
Services are injectable in every class inside your application and since there is (by default) only one instance of that service running, you could just autowire it to your controller.
You have to inject service directly in controller and do data validation in PaymentService. For example:
class MyController extends AbstractController
{
private PaymentRequestService $paymentRequestService;
public function __construct(PaymentRequestService $paymentRequestService)
{
$this->paymentRequestService = $paymentRequestService;
}
public function index()
{
$this->paymentRequestService->method();
}
}
I'am a Brazilian developer, so... sorry for my limited English right away.
Well, in fact my problem is more a convention problem because until now I hadn't use services with Laravel (my apps were that simple so far).
I read about it before ask this question, but nothing helped with this specific situation. I'll try to describe in a objective way.
before that, just a comment: I know about the mistake using just controllers in these example. The ask is really about that mistake.
Well, the actual structure is:
abstract class CRUDController extends Controller {
protected function __construct($data, $validatorData) {
// store the data in a attribute
// create with Validator facade the validation and store too
}
abstract protected function createRecord();
protected function create() {
try {
// do the validation and return an Response instance with error messages
// if the data is ok, store in the database with models
// (here's where the magic takes place) in that store!
// to do that, calls the method createRecord (which is abstract)
$this->createRecord();
// return a success message in an Response instance
}
catch(\Exception $e) {
// return an Response instance with error messages
}
}
}
class UserController extends CRUDController {
public function __construct($data) {
parent::__construct($data, [
'rules' => [
// specific user code here
],
'messages' => [
// specific user code here
],
'customAttributes' => [
// specific user code here
]
]);
}
protected function createRecord() {
$user = new UserModel();
// store values here...
$user->save();
return $user;
}
}
// here's the route to consider in that example
Route::post('/user', 'WebsiteController#register');
class WebsiteController extends Controller {
private $request;
public function __construct(Request $request) {
$this->request = $request;
}
public function register() {
$user = new UserController();
$user->create($this->request);
// here's the problem: controller working with another controller
}
}
class UserAPIController extends Controller {
// use here the UserController too
}
and many other classes that extends CRUDController in the same way...
What I want
I want to create a controller (called here as CRUDController) to reuse methods like the pattern says (create, read, update and delete).
To be really objective here I'll use the create method as an example.
With the code above it seems clear the purpose? I think so... all my controllers have that code of validation equal and reusable. That's the thing.
Besides that, I want to my route of website call another controller (UserController) to store new users... but in the same way, I'll create an API that uses the same controller in the same way (with validations etc). That's the purpose of Responses in the CRUDController (I'll read them in the WebSiteController to resolve what to do, like show a view and in the other hand with the API I'll basically return the Response.
My real problem
Convention and pattern. The MVC pattern is broken here. Controller calling another controller is wrong and I know that.
I want to know what thing I should use! Services? Is that right? I see a lot (really) of examples of services but nothing like that, working with models and reusing code, etc. I never use Services but I know how to use, but I don't know if it's right to these cases.
I really hope that someone can help here and sorry once again for the mistakes with the English. Thanks a lot.
You're calling the CRUD controller a controller but it does not behave as an MVC controller. At best it's just a helper class. You could always do this:
abstract class CRUDManager {
//As you had the CRUDController
}
class UserManager extends CRUDManager {
//As you had the UserController
}
In your AppServiceProvider:
public function boot() {
$app->bind(UserManager::class, function ($app) {
return new UserManager(request()->all()); //I guess that's what you need.
});
}
Whenever you need to use it you can do:
public function register(UserManager $user) {
$user->create();
}
Now one thing to point out. It's not a good idea to initialise the request in the constructor. You should use dependency injection in controller methods. I don't even know if the request is available when the controller is being constructed (I know the session is not). The reason why I say this is that the middleware runs after the controller is constructed and therefore the request may be modified when the controller method is called.
Another note: If you did the original solution because you needed to use certain controller methods, then you can just use the corresponding traits (because the controller itself does not really have many method). I'm guessing a trait like ValidatesRequests would be one you'd need to use.
I'll answer my own question. I use a pattern called Repository Pattern to resolve the problem (or I try to use, because it's the first time using this pattern: maybe I don't use in the right way in every steps).
Files structure
Controllers
UserController.php
Models
UserModel.php
Providers
UserRepositoryServiceProvider.php
Repositories
RepositoryInterface.php
Repository.php
User
UserRepositoryInterface.php
UserRepository.php
Traits
InternalResponse.php
With that structure I did what I wanted in my question without working just with controllers.
I create a trait called InternalResponse. That trait contains a few methods that receive a transaction, validate if it's the case and then return a Response (called "internal" in my logic because the controller will read and maybe change the Response before return it in the end).
The Repository class, which is abstract (because another class must extend it to make sense to use. In this case the class UserRepository will extend...), uses the Trait mentioned.
Well, with it in mind, it's possible to know that the UserController uses the UserRepositoryInterface, that provides an object UserRepository: because the UserRepositoryServiceProvider register this with that interface.
I think there's no need to write code here to explain, because the problem is about an pattern, and these words explain well the problem (in the question) and the resolution with this answer here.
I'll write here a conclusion, I mean, the files structure with comments to explain a little bit more, to end the answer.
Conclusion: Files structure with comments
Controllers
UserController.php
// the controller uses dependency injection and call methods of
// UserRepository, read and changes the Response receveid to finally
// create the final Response, like returning a view or the response
// itself (in the case it's an API controller)
Models
UserModel.php
// an normal model
Providers
UserRepositoryServiceProvider.php
// register the UserRepositoryInterface to
// return a UserRepository object
Repositories
RepositoryInterface.php
// the main interface for the Repository
Repository.php
// the main repository. It's an abstract class.
// All the others repositories must extend that class, because
// there's no reason to use a class Repository without an Model
// to access the database... That class share methods like create,
// read, update and delete, and the methods validate and transaction
// too because uses the trait InternalResponse.
User
UserRepositoryInterface.php
// the interface for UserRepository class
UserRepository.php
// that class extend Repository and uses the UserModel
Traits
InternalResponse.php
// trait with methods like validate and transaction. the method
// validate, read and validate the data receveid for the methods
// create and update. and all the CRUD methods uses the method
// transaction to perform the data to the database and return a
// response of that action.
That's what I do and like I said before, I don't know if it's a hundred percent correct in reference to Repository Pattern.
I hope this can help someone else too.
Thanks for all.
#1
Class controller {
$customer = Model\customer::find($id);
}
#2
Class controller {
$customer = new Model\customer;
$customer = $customer->data($id);
}
Class Model {
public function data($id) {
return self::find($id);
}
}
I'm new in larava, I got a question about MVC query.
I saw many people put query inside of controller like #1
I usually like to put query inside of model class.
any suggestion?
According to me MVC means - Model View Controller.
In a general language Model should contain the Database Query related stuffs whereas in View you should always use variables to populate view data and at last Controller is generally for catching the requests and send the response by doing some logical functionality on request.
<?php
namespace App\Models;
class ExampleModel
{
// All the database related methods like - all, paginate, find, where, etc would goes in the model...
}
In case of Views
<html>
...
YOUR_CONTENT_HERE
...
</html>
and in case of Controller,
<?php
namespace App\Controllers;
class ExampleController
{
public function index()
{
$request = request()->all();
... do some logical work here ...
return response($data);
}
}
Thats what MVC code structure would be like - in my opinion
Hope this helps!
It's ok to use the model's find() method directly to retrieve an entity.
If you are developing a larger application, you should, however, consider moving all the logic into your own classes. The controller should just collect the input from the http request, session etc. and call a method on a service to do the actual work. That way your code is not glued to a controller action or route.
With Slim I group my controllers and generally have an abstract BaseController I extend for each group. I use class based routing:
/* SLIM 2.0 */
// Users API - extends BaseApiController
$app->post('/users/insert/' , 'Controller\Api\UserApiController:insert');
.
.
// Campaigns - extends BaseAdminController
$app->get('/campaigns/', 'Controller\CampaignController:index')->name('campaigns');
and needed to password protect some routes, at other times I needed to have a slightly different configuration. BaseApiController, BaseAdminController... etc. There were times I needed to know which route I was in so I could execute a certain behavior for just that route. In those cases I would have a helper function like so:
/* SLIM 2.0 */
// returns the current route's name
function getRouteName()
{
return Slim\Slim::getInstance()->router()->getCurrentRoute()->getName();
}
This would give me the route name that is currently being used. So I could do something like...
namespace Controller;
abstract class BaseController
{
public function __construct()
{
/* SLIM 2.0 */
// Do not force to login page if in the following routes
if(!in_array(getRouteName(), ['login', 'register', 'sign-out']))
{
header('Location: ' . urlFor('login'));
}
}
}
I cannot find a way to access the route name being executed. I found this link
Slim 3 get current route in middleware
but I get NULL when I try
$request->getAttribute('routeInfo');
I have also tried the suggested:
'determineRouteBeforeAppMiddleware' => true
I've inspected every Slim3 object for properties and methods, I can't seem to find the equivalent for Slim3, or get access to the named route. It doesn't appear that Slim3 even keeps track of what route it executed, it just... executes it.
These are the following methods the router class has and where I suspect this value would be:
//get_class_methods($container->get('router'));
setBasePath
map
dispatch
setDispatcher
getRoutes
getNamedRoute
pushGroup
popGroup
lookupRoute
relativePathFor
pathFor
urlFor
I was hoping someone has done something similar. Sure, there are other hacky ways I could do this ( some I'm already contemplating now ) but I'd prefer using Slim to give me this data. Any Ideas?
NOTE: I'm aware you can do this with middleware, however I'm looking for a solution that will not require middleware. Something that I can use inside the class thats being instantiated by the triggered route. It was possible with Slim2, was hoping that Slim3 had a similar feature.
It's available via the request object, like this:
$request->getAttribute('route')->getName();
Some more details available here
The methods in your controller will all accept request and response as parameters - slim will pass them through for you, so for example in your insert() method:
use \Psr\Http\Message\ServerRequestInterface as request;
class UserApiController {
public function insert( request $request ) {
// handle request here, or pass it on to a getRouteName() method
}
}
After playing around I found a way to do it. It may not be the most efficient way but it works, and although it uses Middleware to accomplish this I think there are other applications for sharing data in the Middleware with controller classes.
First you create a middleware but you use a "Class:Method" string just like you would in a route. Name it whatever you like.
//Middleware to get route name
$app->add('\Middleware\RouteMiddleware:getName');
Then your middleware:
// RouteMiddleware.php
namespace Middleware;
class RouteMiddleware
{
protected $c; // container
public function __construct($c)
{
$this->c = $c; // store the instance as a property
}
public function getName($request, $response, $next)
{
// create a new property in the container to hold the route name
// for later use in ANY controller constructor being
// instantiated by the router
$this->c['currentRoute'] = $request->getAttribute('route')->getName();
return $next($request, $response);
}
}
Then in your routes you create a route with a route name, in this case I'll use "homePage" as the name
// routes.php
$app->get('/home/', 'Controller\HomeController:index')->setName('homePage');
And in your class controller
// HomeController.php
namespace Controller;
class HomeController
{
public function __construct($c)
{
$c->get('currentRoute'); // will give you "homePage"
}
}
This would allow you to do much more then just get a route name, you can also pass values from the middleware to your class constructors.
If anyone else has a better solution please share!
$app->getCurrentRoute()->getName();
$request->getAttribute('route')->getName();
What I want:
If session is not set, I want to redirect to zf2.localhost/authUser/index
When I'm using toRoute in CreatorController everything works.
However, I want to use this method in several files. When I attempt to call toRoute in my SessionServiceController, I get the following error.
Error:
Url plugin requires that controller event compose a router; none found
Could you write me what I'm doing wrong?
Code:
CreatorController (zf2.localhost/creator/index)
<?php
namespace Creator\Controller;
use Zend\View\Model\ViewModel;
use Zend\Mvc\Controller\AbstractActionController;
use Creator\Controller\SessionServiceController;
class CreatorController extends AbstractActionController{
public function __construct(){
$this -> sessionService = new SessionServiceController();
}
public function indexAction(){
if($this-> sessionService ->checkSession()){
echo 'Witam';
}
}
}
SessionServiceController:
<?php
namespace Creator\controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Session\Container;
use Creator\Controller\SessionServiceController;
class SessionServiceController extends AbstractActionController {
const CONTAINER_SESSION_ID = 'usr_id';
public function __construct(){
$this -> session_id = new Container( self::CONTAINER_SESSION_ID);
}
public function checkSession(){
if (empty($this ->session_id->usr_id)){
//$this->redirect()->toUrl("zf2.localhost/authUser/index");
return $this->redirect()->toRoute('creator/default',
array('controller'=>'authUser', 'action'=>'index'));
}
if (!empty($this ->session_id->usr_id)){
return true;
}
}
}
You don't want to directly instantiate your controllers, use the ServiceManager. For toRoute to work, the Url helper needs a instance of the router. This is usually handled by the ControllerPluginManager when a controller is called from the ControllerManager.
But, beyond that, this logic is probably in the wrong place. If you're checking for authenticated users, you probably want to load and utilize zend's authentication service in an event before the controller is even loaded. I would guess the best place is somewhere in your ON_ROUTE event loop, but I can't speak to your specific application.
You will know if a user is logged in or not very early in the request chain, no reason to boot a controller. Take a peek at ZfcUser (https://github.com/ZF-Commons/ZfcUser), it already has versions of auth redirect listeners you can use as inspiration for your own (or you might find you just want to use that project for what you're doing)