Model binding in nested resources in Laravel - php

I am building a RESTful api in Laravel, with my front end app in Angular.
I have a number of models / controllers and routes. I am struggling to get to grips with creating restful route for nested resources.
For example, my route to show a list of clients is simple:
Route::resource('clients', 'ClientsController');
A client can have many campaigns:
class Client extends Model
{
public function campaigns()
{
return $this->hasMany('App\Campaign');
}
}
I do not need a route to show all campaigns, but I do need a route to show all campaigns based on a client.
Route::resource('clients.campaigns', 'CampaignsController');
My intention was for the endpoint requested by the Angular app to be:
myapp/api/clients/6/campaigns
Where '6' is the client ID. This would return a list of campaigns that belong to client with id 6.
In my index method, I am trying to use implicit model binding to get this client Id, but I always get an empty result set:
class CampaignsController extends ApiController
{
public function index(Client $client)
{
$campaigns = Campaign::where('client_id', $client->id)->get();
if (!count($campaigns)) {
Log::warning('Campaigns list is empty');
return $this->respondNotFound('No Campaigns Found');
}
try {
return $this->respond([
'data' => $this->campaignTransformer->transformCollection($campaigns->all())
]);
} catch (\Exception $e) {
Logging::logException($e, 'API error showing campaigns list');
return $this->respondInternalError('An internal error occurred');
}
}
}
Clearly my route isn't binding a client - a var_dump on $client shows this.
Where am I going wrong?

For anyone who comes across this issue - Laravel was injecting the client_id for me.
Therefore index method becomes:
public function index($client_id)
{
$campaigns = Campaign::where('client_id', $client_id)->get();
}

Related

Laravel Resource controller

How do I tell my API to display a particular result based on another column?
e.g. localhost:8000/api/gadgets/{{id}}
Normally it returns the particular information of the specific gadget with that ID and localhost:8000/api/gadgets/{{imei_code}} does not return any value or an error whereas imei_code is a column that I needed to pass as a GET request...
I'm using the normal resource controller
public function show(Gadgets $gadget)
{
$response = ['data' => new GadgetResource($gadget), 'message' => 'specific gadget'];
return response($response, 200);
}
Also I need help on how I can create like a search function in the controller.
You can`t do two similar URLs. I think your route for URL
localhost:8000/api/gadgets/{{imei_code}}
isn`t work. Also the order of the routes is important and route that defined firstly will be have higer priority then route that defined secondly.
Because your routes /api/gadgets/{{id}} and /api/gadgets/{{imei_code}} is similar in this case only the one described earlier will be processed.
You can define another router and handler, for example:
localhost:8000/api/gadgets
That will return a list of gadgets by default and you can add filters for imei_code. For example:
localhost:8000/api/gadgets?imei_code=123
And your handler for the new route may be writed something like that:
public function showList(Request $request): GadgetResource
{
if ($imeiCode = $request->query('imei_code')) {
$list = Gadget::query()->where('imei_code', $imeiCode)->get();
} else {
$list = Gadget::query()->take(10)->get();
}
return GadgetResource::collection($list);
}
Or like alternative solution you can create diferent route for searching of gadgets exactly by imei_code to get rid of any route conflicts
localhost:8000/api/gadgets/by_imei/123
public function findByImei(Request $request): GadgetResource
{
$imeiCode = $request->route('imei_code');
$item = Gadget::query()->where('imei_code', $imeiCode)->first();
return new GadgetResource($item);
}
You can specify the model key by scoping - check docs
Route::resource('gadgets', GadgetController::class)->scoped([
'gadget' => 'imei_code'
]);
Than, when Laravel try to bind Gadget model in Controller - model will will be searched by key imei_code.
This code equvalent of
Route::get('/gadget/{gadget:imei_code}');
Try to change response
public function show(Gadgets $gadget)
{
$response = ['data' => new GadgetResource($gadget), 'message' => 'specific gadget'];
return response()->json($response);
}

Laravel Won't Persist Post in Controller using EmberJs

I have a problem with one of my controllers either in Laravel or Ember. I can get the record to save after filling out the form but the record will not persist to the database. Again the form saves the record but doesn't push the record to the database. I tried following Embers guides on pushing data to the server but no juice. Also thanks for all the help from this site, you have got me this far, hopefully I can get this worked out with your help. Here are the controllers,
Laravel Controller
$statusCode = 200;
$libraries = $request->all();
$library = Library::create($libraries);
$criteria = $library->toArray();
return Response::json([
'libraries' => $criteria],
$statusCode);
Ember Route
model() {
return this.store.createRecord('library');
},
actions: {
saveLibrary(item) {
item.save().then(() => this.transitionTo('libraries'));
},
willTransition() {
//rollbackAttributes() removes the record from the store
// if model is 'isNew'
this.controller.set('model').rollbackAttributes();
}
}
In controller, you should use transitionToRoute. and implement POST request for /libraries for save method call.
actions: {
saveLibrary() {
//You dont need to pass. you need to update model properties. and then call save method.
this.get('model').save().then(() => this.transitionToRoute('libraries'));
}
}

Android App interaction with Laravel Action methods

I already have Laravel web pages where i can add/update/delete/Read records from MySQL Database. Laravel version is 5.2.15
Now, I have to integrate Database with Android App. In order to do that I have to post and read Json Data.
Here question is: Should I have 2 public action methods? First for web page that will show records on webpage and second will return json data in Android.
I meant, when I return data to webPage..I will have to write the below code.
return View("View-Path", array("Data" => $Data));
but in case of Android App, I will have to request Json Data.
Please suggest the right approach.
You should develop a simple API to access your APP data from an android client:
Routes
First of all you need to create some specific routes for the API through which you'll serve your data in JSON format
Authentication
The API's routes should handle authentication in a different way in respect on what you're doing now: you can't use the classic session-based approach. Instead you have to use a basic or token-based approach. You've different alternatives, these are some of the most used (from the simplest, to the most complicated )
Laravel HTTP Basic Authentication
Laravel Json Web Token Authentication
Laravel OAUTH2
Data Acess
Once you've setup your routes and authentication, you have to serve your data via the API routes. Since you use the same data in your APP routes and API routes, you can wrap the logic of data building and retrieving in services, and use the services to get the data both in your APP routes and API routes.
Using different controllers for API and APP routes, you have:
//APP Controller method for route: www.app.com/app-route/users
public function getUsers()
{
//wrap the logic to build the data inside the service
$service = App::make('your_service');
//once is ready, get the built data from the service
$data = $service->getData();
return View("View-Path", array("Data" => $data));
}
//API Controller method for route: www.app.com/api/users
public function getUsers()
{
//use the same service to build and get the data you need
$service = App::make('your_service');
$data = $service->getData();
return response()->json( $data );
}
This way you can:
Encapsulate data building and retrieveng in services, so that you don't have the need to duplicate code for data retrieving between APP and API routes
Have different controllers to access APP or API routes; so you can get the data, transform it as you need and serve it to either views or api clients
About the Service class
Regarding the service class i've mentioned, it could be simply one or multiple wrapper classes that you use both in API and APP controllers to build and get the data without repeting code. The structure of such classes depends on how your app work.
For example let's suppose you need to compute some data for each user's project, store it in a variable and then send it to the viev:
public function getUsers($request)
{
$user = Users::with('projects')->find( $request->user_id )->get();
$data = [];
foreach ( $user->projects as $p )
{
//compute some data you need and store it in $data;
}
return View("View-Path", array("Data" => $data));
}
Now if want to make the same thing in the API controller, you'd need to repete this code to get the projects and create the data. To avoid this, you could 'wrap' the data access in a service class, and use the same class in boh controllers:
Service class
public class UserDataBuilder
{
protected $user;
public function setUser( Authenticatable $user )
{
$this->user = $user;
}
public function getData()
{
$user = Users::with('projects')->find( $this-user->id )->get();
$data = [];
foreach ( $user->projects as $p )
{
//compute some data you need and store it in $data;
}
return $data;
}
}
and use the same class in both API and APP controllers:
//APP controller: get the data and pass to the view
public function getUsers($request)
{
$service = App::make( UserDataBuilder::class );
$service->setUser( User::find( $request->user_id )->get() );
return View("View-Path", array("Data" => $service->getData() );
}
//API controller: get the data and convert to JSON
public function getUsers($request)
{
$service = App::make( UserDataBuilder::class );
$service->setUser( User::find(1)->get() );
return response()->json( $data );
}
For android you have to write a separate web service.
You can use one method to do that. Example
public function someMethod(Request $request){
$Data = ['laravel','is','awesome'];
// If the request has the content type is json return a json response
// So you android request will be true for this request
// else return data to webpage
if($request->isJson()){
return response()->json($Data);
}
return View("View-Path", array("Data" => $Data));
}

Consistent REST API Response in Laravel+Dingo

I have been developing a set of rest APIs to be exposed for mobile apps. I am following the repository pattern for the development on the Laravel project. How do I implement a presenter and transformer for formatting a constant JSON output throughout the set of all my APIs?
For example I have the following controller for login
public function authenticate()
{
$request = Request::all();
try {
// If authenticated, issue JWT token
//Showing a dummy response
return $token;
} catch (ValidatorException $e) {
return Response::json([
'error' =>true,
'message' =>$e->getMessageBag()
]);
}
}
Now where does a transformer and presenter come into the picture? I know that both are used to format the output by converting the db object and produce a formatted JSON so that it remains uniform across my APIs.
The dingo API and fractal or even the framework (L5 repository) don't provide detailed documentation and I can't find any tutorials on this.
I have created the following presenter and transformer for another API which gives the list of products
namespace App\Api\V1\Transformers;
use App\Entities\Product;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract {
public function transform(\Product $product)
{
return [
'id' => (int) $product->products_id
];
}
}
Presenter
<?php
namespace App\Api\V1\Presenters;
use App\Api\V1\Transformers\ProductTransformer;
use Prettus\Repository\Presenter\FractalPresenter;
/**
* Class ProductPresenter
*
* #package namespace App\Presenters;
*/
class ProductPresenter extends FractalPresenter
{
/**
* Transformer
*
* #return \League\Fractal\TransformerAbstract
*/
public function getTransformer()
{
return new UserTransformer();
}
}
How will I set the presenter in the controller and respond back? Tried
$this->repository->setPresenter("App\\Presenter\\PostPresenter");
But it doesn't seems to work and the doc doesn't shows the complete steps.
In the above example, how can I make a template for an error response which I can use throughout my APIs and how will I pass my error exceptions to it?
It seems like presenter and transformer can be used to convert database objects into presentable JSON and not anything else. Is that right?
How do you use a presenter and a transformer for a success response and an error response? By passing exceptions, instead of DB objects to the transformer?
I had the same exact problem and here is how I used dingo with transformer
Controller:
public function update(Request $request)
{
$bus = new CommandBus([
$this->commandHandlerMiddleware
]);
$agency = $bus->handle(
new UpdateAgencyCommand($request->user()->getId(), $request->route('id'), $request->only('name'))
);
return $this->response->item($agency, new AgencyTransformer());
}
Transformer:
class AgencyTransformer extends TransformerAbstract
{
public function transform(AgencyEntity $agencyEntity)
{
return [
'id' => (int) $agencyEntity->getId(),
'name' => $agencyEntity->getName(),
];
}
}
and this is how I handle errors:
throw new UpdateResourceFailedException('Could not update agency.', $this->agencyUpdateValidator->errors());
I just now see your similar question here as well. So see my answer on your other question here: https://stackoverflow.com/a/34430595/429719.
From the other question I derived you're using Dingo, so use that as a structured response class. Make sure you're controller extends from Dingo and then you can just return items and collections in a structured way like:
return $this->response->item($user, new UserTransformer);
return $this->response->collection($users, new UserTransformer);
If you want a nice error handling look for the docs here: https://github.com/dingo/api/wiki/Errors-And-Error-Responses
Basically you can throw any of the core exceptions or a few custom Dingo ones. The Dingo layer will catch them and returns a structured JSON response.
As per the Dingo docs:
throw new Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException('Nope, no entry today!');
Will generate:
{
"message": "Nope, no entry today!",
"status_code": 403
}
You can try use,[Midresapi]: https://github.com/oktorino/midresapi. this will return consistens success or failled response, work in laravel 7 & 8 , Handling validation response, handling 500 response:
$users=\App\User::latest()->limit(2)->get();
return response($users);
#or
return fractal()
->collection($users)
->transformWith(new \App\Transformers\UserTransformer)
->toArray();
Response :
{
"status_code": 200,
"success": true,
"message": "ok",
"data": [
{
"username": "dany",
"email": "jancuk#sabarbanget.com"
},
{
"username": "scc-client-5150",
"email": "dancuk#gmail.com"
}
]
}
Fractal is fully documented here: http://fractal.thephpleague.com/
There is an excellent book that I regularly read from the Phil Sturgeon https://leanpub.com/build-apis-you-wont-hate You can find most of the books code available in github https://github.com/philsturgeon/build-apis-you-wont-hate. You can find really nice examples of Fractal in there.
I would create an Api Controller and extend it from my Controllers.In there there should be all the respond functions (respondWithError, respondWithArray etc.)
Tranformers are transforming objects in a consistent json format so that all your endpoints return the same thing for each entity.
Dont really have an answer on this
There are enough examples in Fractal documentation.

How to return a JsonModel error from a dispatch event in Zend Framework 2?

WHAT I'M TRYING TO DO
I have a PHP application using the Zend Framework 2 and it has a "visual" side (where it returns nice HTML web pages) and an API side (where it simply returns JSON). When a request is sent to our server it's routed through the onBootstrap( \Zend\Mvc\MvcEvent ) function in Module.php. The bootstrap function does some general setup and checks (is the user signed in...etc.) and then attaches a dispatch function for \Zend\Mvc\Controller\AbstractRestfulController's (our API controllers) and another dispatch function for \Zend\Mvc\Controller\AbstractActionController(our "Visual" controllers).
In the dispatch functions more session specific stuff is checked and setup and occasionally an error can occur. If an error occurs in the \Zend\Mvc\Controller\AbstractActionController's dispatch function then it simply returns the error message to the view and the view displays it to the user. If an error occurs in the \Zend\Mvc\Controller\AbstractRestfulController's dispatch function I want it to return a JsonModel with the error information and an appropriate response header (400,404...etc).
WHAT'S HAPPENING
When an error occurs in the \Zend\Mvc\Controller\AbstractRestfulController's dispatch function the response header is set but the body isn't. On top of that the action is still routed to the controller so if the controller specifies a new response header then that overrides the previous one.
Here's an excerpt of my code:
public function onBootstrap( \Zend\Mvc\MvcEvent $event ) {
...
$event_manager = $event->getApplication()->getEventManager();
$shared_manager = $event_manager->getSharedManager();
...
// Dispatch event for AbstractRestfulController calls
$shared_manager->attach('Zend\Mvc\Controller\AbstractRestfulController', 'dispatch', function($event) {
...
try {
$organization = $organization_interface->get($id);
} catch(Exception $e) {
$event->getResponse()->setStatusCode($e->getCode());
return new JsonModel(array(
'error' => $e->getMessage(),
));
}
...
}, 100);
...
}
So I know that the returned JsonModel won't work because it's being returned from the dispatch function and not the controller. What I want to know is an "easy" way to send my JsonModel as the response AND stop the framework from running the routed action in the controller.
Any and all help is appreciated. Thanks!
If you are looking for the string to be returned from that JSON Model then this should be enough -
It is obvious to get the action getting dispatch and not the JSON Model, so try this -
instead of -
return new JsonModel(array(
'error' => $e->getMessage(),
));
write
$view = new JsonModel(array(
'error' => $e->getMessage(),
));
echo $view->serialize();
http_response_code($e->getCode()); //Added the line of code as per suggested in the comment by B1NARY
exit();
This will return the JSON string.
Let us know if this is not what you are looking for.
This is, for me, a better zf2ish solution :
$this->response->setStatusCode(Response::STATUS_CODE_401);
$viewModel = new JsonModel(['error' => 'Unauthorized', 'error_description' => $exception->getMessage()]);
$event->setViewModel($viewModel);
$event->stopPropagation(true);
return $viewModel;

Categories