In Laravel no way to replace the default structure for paginated responses. This is the structure I'm trying to achieve:
return response()->json([
'data' => $items->items()
'meta' => [
'current_page' => $items->currentPage(),
'from' => $items->firstItem(),
'last_page' => $items->lastPage(),
'per_page' => $items->perPage(),
'to' => $items->lastItem(),
'total' => $items->total(),
];
]);
I have solved this issue before with resource collection. This is not supported by Laravel out of the box so it needs a bit of work.
First, you got to override the App\Http\Resources\PaginatedResourceResponse class, which is the default way to present paginated response. Then you can override the default structure of the returned paginated data.
class CustomPaginatedResourceResponse extends PaginatedResourceResponse
{
}
Then pls create a resource collection that uses the custom paginated resource.
class ItemsResource extends ResourceCollection
{
public function toArray($request)
{
}
// Override the toResponse method.
public function toResponse($request)
{
return $this->resource instanceof AbstractPaginator
? (new CustomPaginatedResourceResponse($this))->toResponse($request)
: parent::toResponse($request);
}
}
Finally, you can simply use the ItemsResource in your controller.
return new ItemsResource($items);
This requires understanding of the api resources and the source code. Pls spend some time to read the docs and figure out how the App\Http\Resources\PaginatedResourceResponse is used.
Related
I'm using the Log:: facade a lot and have a helper class called LogHelper which provide me with a static method LogHelper::context() which include many key values I need to track the requests. But having to type it every time for each usage make it error prune and fill not so efficient.
I'm looking for a way to inject the values by default, and allow me to overwrite them if needed specifically.
At the moment this is how I use it,
Log::debug('Request Started', LogHelper::context());
what I'm looking for is to inject the context by default
Log::debug('Request Started');
and have the option to overwrite it, if need it:
Log::debug('Request Started', ['more' => 'context'] + LogHelper::context());
PS, the LogHelper::context() return a simple key => value array which include some staff i need to debug requests, and the reason it do not use the values directly in the message is because i log to graylog as structured data, and this way i can filter by any key.
I have solved this issue by using the tap functionality and $logger->withContext() (note: the latter was added in Laravel 8.49).
You want to create a new class which contains your context logic. I've created an extra Logging folder in app/ in which my logging customizations sit.
app/Logging/WithAuthContext.php:
<?php
namespace App\Logging;
use Illuminate\Log\Logger;
class WithAuthContext
{
public function __invoke(Logger $logger)
{
$logger->withContext([
'ip' => request()?->ip(),
'ua' => request()?->userAgent(),
]);
}
}
Depending on which logging channel(s) you use, you will have to add the class to each one you want to add context to. So in app/config/logging.php:
<?php
use App\Logging\WithAuthContext;
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
// ...
'channels' => [
// ...
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'tap' => [WithAuthContext::class],
],
// ...
],
];
There is a way, but it is not pretty. You can create a custom monolog logger driver. The process is described at https://laravel.com/docs/8.x/logging#creating-monolog-handler-channels.
Here's a possible implementation:
class ContextEnrichingLogger extends \Monolog\Handler\AbstractHandler {
private $logger;
public function __construct($level = Monolog\Logger::DEBUG, bool $bubble = true, $underlyingLogger = 'single') {
$this->logger = Log::driver($underlyingLogger);
}
public function handle(array $record) {
$record['context'] += LogHelper::context();
return $this->logger->handle($record);
}
}
Then register this as a custom logger in your config/logging.php:
return [
'default' => 'enriched',
//...
'channels' => [
// ...
'enriched' => [
'driver' => 'monolog',
'handler' => ContextEnrichingLogger::class,
'level' => env('APP_LOG_LEVEL', 'debug'),
"with" => [
"underlyingLogger" => env('LOG_CHANNEL', 'single')
]
]
]
];
I haven't tested this particular one but this is how I've defined other custom loggers.
Note, this is probably also achievable via a custom formatter though I think it's probably the same trouble.
I'm learning Laravel and have created a public endpoint where I want to output only certain information of some comments if a user is not authenticated from a GET request.
I have managed to filter out the comments based on whether or not they are approved. I now want to filter out the data that is returned. I have attached a screenshot of what is currently returned.
Ideally, I only want to return the id, name and the body in the json. How can I go about this? I tried the pluck() method which did not give the desired results. Any pointers would be greatly appreciated
public function index(Request $request)
{
if (Auth::guard('api')->check()) {
return Comment::all();
} else {
$comments = Comment::where('approved', 1)->get();
return $comments->pluck('id','name','body');
}
}
To select the particular columns, you can pass columns name to get as
$comments = Comment::where('approved', 1) -> get(['id','name','body']);
You can use a transformer to map the incoming data to a sensible output based on the auth state. The following example comes from the Fractal lib:
<?php
use Acme\Model\Book;
use League\Fractal;
$books = Book::all();
$resource = new Fractal\Resource\Collection($books, function(Book $book) {
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => $book->yr,
'author' => [
'name' => $book->author_name,
'email' => $book->author_email,
],
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
]
];
});
Ideally, you would create 2 classes that extend from Transformer and pass the correct one to the output.
If you want to pass the result as json respose
$comments = Comment::where('approved', 1)->pluck('id','name','body')->toArray();
return Response::json($comments);
If you want to pass the result as to blade
$comments = Comment::where('approved', 1)->pluck('id','name','body')->toArray();
return view('your_blade_name')->with('comments',$comments);
I have difficult to build and return a nested json. I want obtain the information from two differents table joined with an id.
This is my situation:
With this method on my controller:
public function eventOccList(EventOccurrence $eventOccurrence){
return new EventOccurrenceResourceCollection(EventOccurrence::all());
}
and with the mapping in the class EventOccurrenceResource
return [
'type' => 'event',
'id' => (string) $this->id,
'name' => $this->name,
'description' => $this->description,
'location_id' => $this->location_id
];
I obtain this JSON:
{"data":[{"type":"event","id":"1","name":"event_1","description":"event blabla","location_id":11}
If I want to obtain all the informations about the table "location" with the id "location_id" and show in the same json, what is the best way to retrieved this data?
Thanks !
I assume your Event Model have a location relationship :
public function location{
return $this->belongsTo(Event::class);
}
you can do this once you have your event in a Controller :
$event->load('location');
return $event->toJson();
You can then hide or append any attribute you want :)
I am using fractal for my small project, here is my code:
public function transform(Series $series) {
return [
'id' => $series->id,
'title' => $series->title,
'url' => $series->url,
'description' => $series->description,
'thumbnail_hd' => $series->thumbnail_hd,
'thumbnail_wide' => $series->thumbnail_wide,
'views' => $series->views
];
}
I would like to make views (which is an int) optional and not return the views unless requested - since this field is based on a relationship and will increase the processing time.
I would like to use it as relationships (so i can include particular fields whenever I need to):
// in transformer
public function includeUser(Series $series) {
return $this->item($series->user, new UserTransformer);
}
// in controller
return fractal()
->item($series)
->parseIncludes(['user'])
->transformWith(new SeriesTransformer)
->toArray();
But just for an integer instead of a whole array of data. Is it possible using Fractal?
What you can do is the following
public function transform(Series $series) {
$return = [
'id' => $series->id,
'title' => $series->title,
'url' => $series->url,
'description' => $series->description,
'thumbnail_hd' => $series->thumbnail_hd,
'thumbnail_wide' => $series->thumbnail_wide,
];
if ($series->views > 0) {
$return['views'] = (int) $series->views;
}
return $return;
}
I wouldn't suggest doing this though. I would usually just return views as 0.
If you're worried about your DB performance, your app is going to have to count the views anyway to know if they're greater than 0.
If you worried about client side performance, this is not going to matter with a single key value pair.
I am using zf2 restful api in my web services.
This is my code -
module.config.php -
'login' => array(
'type' => 'segment',
'options' => array(
'route' => '/ws/login[/:id]',
'defaults' => array(
'__NAMESPACE__' => 'Webservices\Controller',
'controller' => 'Login',
),
),
),
This is my controller -
<?php
namespace Webservices\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\View\Model\JsonModel;
class LoginController extends AbstractRestfulController {
public function getList() {
return new JsonModel(array(
'data' => '',
));
}
/**
* params time, language
* listing category details
* return category details
*/
public function get($id) {
return new JsonModel(array(
'data' => '',
));
}
public function create($requestData) {
print_r($requestData);
die();
}
}
When I post some data into this controller then it redirects into create function.
But requestData variable is NULL.
Raw data method is used for posting. This is my request data
{"reqType":"2","verNo":"test","userName":"test==","deviceIdentifier":"DKZWcdvB50+test+test","password":"test=="}
For some technical reasons I am still using php 5.3.3 and zf2.0.
Sorry I don't have privileges to do comment your question so I just write this as an answer even if this is not.
I'm not sure if your routing is well configured in module.config.php.
There is no action defined in the routing.
Also in your controller the name of the create function shouldn't be createAction ??
I haven't tried but I guess if we're talking about actions in the controller then you're not able to use the parameter list of the action function (only if you defined them in your routing properly). Use instead the following: $this->params()->fromPost(parameterName)
If I misunderstood anything please let me know but I think this problem is not actually related with the zf2 restful API instead how to do routing in zf2 and how to get the parameters inside the actions.