I have 2 variables that I want to send to resource and show them via API like below:
return new RoomDetailResource($data_array, $sum);
and the resource I have written is like below:
class RoomDetailResource extends JsonResource
{
/**
* #var
*/
public $sum;
/**
* Create a new resource instance.
*
* #param mixed $resource
* #return void
*/
public function __construct($resource, $sum)
{
// Ensure you call the parent constructor
parent::__construct($resource);
$this->resource = $resource;
$this->sum = $sum;
}
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'sum' => $this->sum
];
}
}
now what I get is the error:
"message": "Trying to get property 'id' of non-object", "status_code": 500,
but if I want to show sum it shows without any problem now here is how I want my API response to look like:
{
id: 1,
sum: 200
},
{
id: 2,
sum: 200
}
Note that sum is the same for all and I just want to repeat it in objects or show them at the end of the API response as a property.
thanks
Related
I try to create a method that returns an array of IDs:
/**
* #ORM\OneToMany(targetEntity="App\Entity\Event", mappedBy="project")
*
* #Serializer\Expose()
*/
private $event;
public function __construct()
{
$this->enabled = false;
$this->event = new ArrayCollection();
}
/**
* #return array<string, mixed>
*
* #Serializer\VirtualProperty()
* #Serializer\SerializedName("event")
*/
public function getEvent(): ?array
{
if ($event = $this->getEvent()) {
return [
'id' => $event->getId(),
];
}
return null;
}
But I get an error message in my console:
The "selection" field with the path "event/" expects an array of ids
as value but received an array of objects instead. Is it possible that
your API returns an array serialized objects?
To fix this I tried:
public function getEvent()
{
return $this->event;
}
/**
* #return array<string, mixed>
*
* #Serializer\VirtualProperty()
* #Serializer\SerializedName("event")
*/
public function getEventData(): ?array
{
if ($event = $this->getEvent()) {
return [
'id' => $event->getId(),
];
}
return null;
}
But here I get the error message:
Attempted to call an undefined method named "getId" of class
"Doctrine\ORM\PersistentCollection".
$event is a OneToMany relationship, which mean it would be better if you renamed it $events.
$event is an ArrayCollection made of Event object.
Which mean that if you want to get an array made only of ids, you need to turn your ArrayCollection into an array of ID.
So you could use ArrayCollection map method to apply a callback on each element to return an array of id.
Example:
public function getEventData(): ?array
{
return $this->event->map(fn($e) => $e->getId())->toArray();
}
map (from doctrine doc)
Applies the given function to each element in the collection and
returns a new collection with the elements returned by the function.
We use toArray to turn the ArrayCollection into an array.
I have my controller with index() and show().
{
/**
* Display a listing of the resource.
*
* #return \App\Http\Resources\MyRessource
*/
public function index()
{
//???
}
/**
* Display the specified resource.
*
* #param \App\Models\Test $test
* #return \App\Http\Resources\TestRessource
*/
public function show(Test $test)
{
return new \App\Http\Resources\TestRessource($test);
}
}
In my resource the show() has the format I want for return, so the result for http://127.0.0.1/Test/1 is the ID 1 with the formatted JSON.
{
"data": {
"id": 1,
"ref": "0103573026466442101007175850",
"tax": null,
"date_in": "2021-10-08T12:37:05.000000Z",
"date_out": "2021-10-11T08:02:17.000000Z"
}
}
I want the index() to return the same way by using my resource.
When I do index() on http://127.0.0.1/Test, it returns all my data but not in the formatted JSON that I want.
Resource code:
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
"id" => $this->id,
"ref" => $this->ref,
"tax" => $this->tax,
"date_in" => $this->date_in,
"date_out" => $this->date_out
];
}
On index() do as in docs.
return TestRessource::collection(Test::all());
I have a very specific case in Laravel framework pagination.
Imagine I get my results from a Redis API by passing offset and limit parameters. In the other words, the paginating stuff is done on the API side.
Now when I get the results in my Laravel app I want to show them in a pagination. I mean a simple pagination view that offers a navigation to the other pages. for example, the second page means I have to send a request to my Redis API in order to get the second set of data.
Based on what I understand From Laravel paginator class, it needs a collection of items and gives a convenient pagination over them. Something that is a bit different from what I want.
I need a Class only for making the pagination view which gets a total number of items as a parameter and makes the appropriate links layout.
Is there a convenient way to do this in Laravel?
or
is implementing it by myself my only option?
I'm using below class to get data in pagination using data:-
namespace App\Helpers;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Collection;
use Countable;
use ArrayAccess;
use ArrayIterator;
use JsonSerializable;
use IteratorAggregate;
use Illuminate\Pagination\LengthAwarePaginator;
class LengthAwareOffsetPaginator extends LengthAwarePaginator implements
Arrayable,
ArrayAccess,
Countable,
IteratorAggregate,
JsonSerializable,
Jsonable
{
protected $items;
protected $total;
protected $total_pages;
protected $limit;
protected $offset;
protected $options;
/**
* LengthAwareOffsetPaginator constructor.
*
* #param Collection $items
* #param $total
* #param $limit
* #param $offset
* #param array $options
*/
public function __construct(Collection $items, $total, $limit, $offset, array $options = [])
{
$this->items = $items;
if ($items->count() > $limit) {
$this->items = $items->take($limit);
}
$this->total = $total;
$this->limit = $limit;
$this->offset = $offset;
$this->options = $options;
$this->total_pages = ($total/$limit);
}
/**
* Get url of an offset.
*
* #param int $offset
*
* #return string Url of an offset
*/
public function url($pageNumber)
{
$query = isset($this->options['queryParameter']) ? $this->options['queryParameter'] : [];
$offset = ($pageNumber - 1) * $this->limit;
$query = array_merge($query, ['page' => ['limit' => $this->limit, 'offset' => $offset]]);
$url = isset($this->options['path']) ? $this->options['path'] : '/';
return $url.'?'.http_build_query($query);
}
/**
* Get last page.
*
* #return int Last page
*/
public function lastPage()
{
$totalPages = ceil($this->total / $this->limit);
return $totalPages;
}
/**
* Get last page offset.
*
* #return int Last page offset
*/
public function totalPages()
{
return $this->total_pages;
}
/**
* Get current page.
*
* #return int Last page offset
*/
public function currentPage()
{
$pages = (int)ceil($this->offset / $this->limit);
$currentPage = ($pages + 1);
return $currentPage;
}
public function perPage()
{
return $this->limit;
}
/**
* Get last page url.
*
* #return string
*/
public function lastPageUrl()
{
$last = $this->lastPage();
return $this->url($last);
}
/**
* get next page url.
*
* #return string
*/
public function nextPageUrl()
{
$nextOffset = $this->offset + $this->limit;
return ($nextOffset >= $this->total)
? null
: $this->url($nextOffset);
}
/**
* get previous page url.
*
* #return string
*/
public function previousPageUrl()
{
if ($this->offset == 0) {
return null;
}
$prevOffset = $this->offset - $this->limit;
return ($prevOffset < 0)
? $this->url($prevOffset + $this->limit - $this->offset)
: $this->url($prevOffset);
}
public function items()
{
return $this->items;
}
/**
* get total items.
*
* #return int
*/
public function total()
{
return $this->total;
}
/**
* Get the number of items for the current page.
*
* #return int
*/
public function count()
{
// return $this->total;
return $this->items->count();
}
/**
* Get an iterator for the items.
*
* #return \ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->items->all());
}
/**
* Determine if the given item exists.
*
* #param mixed $key
*
* #return bool
*/
public function offsetExists($key)
{
return $this->items->has($key);
}
/**
* Get the item at the given offset.
*
* #param mixed $key
*
* #return mixed
*/
public function offsetGet($key)
{
return $this->items->get($key);
}
/**
* Set the item at the given offset.
*
* #param mixed $key
* #param mixed $value
*/
public function offsetSet($key, $value)
{
$this->items->put($key, $value);
}
/**
* Unset the item at the given key.
*
* #param mixed $key
*/
public function offsetUnset($key)
{
$this->items->forget($key);
}
/**
* Get the instance as an array.
*
* #return array
*/
public function toArray()
{
return [
'first' => $this->url(0),
'last' => $this->lastPageUrl(),
'next' => $this->nextPageUrl(),
'prev' => $this->previousPageUrl(),
'data' => $this->items->toArray(),
];
}
/**
* Convert the object into something JSON serializable.
*
* #return array
*/
public function jsonSerialize()
{
return $this->toArray();
}
/**
* Convert the object to its JSON representation.
*
* #param int $options
*
* #return string
*/
public function toJson($options = 0)
{
return json_encode($this->jsonSerialize(), $options);
}
}
you need to call this like:
$options['queryParameter'] = [
'page' => [
'limit' => 10,
'offset' => 0
],
'path' => \Illuminate\Pagination\Paginator::resolveCurrentPath()
];
$result = new LengthAwareOffsetPaginator(
collect($data),
$totalItemsCount,
$this->limit,
$this->offset,
$options
);
This will give you following output:
{
"data": [
{
....
},
{
....
}
],
"meta": {
"pagination": {
"total": 110,
"count": 10,
"per_page": 10,
"current_page": 1,
"total_pages": 11,
"links": [
"self": "url/pages?page=1",
"next": "url/pages?page=2",
"first": "url/pages?page=1",
"last": "url/pages?page=11"
]
}
}
}
I think this will help you.
I have try to create a Custom REST POST plugin in my Drupal 8.3.2 for get an external JSON and then create an article from that.
I have follow that guide: How to create Custom Rest Resources for POST methods in Drupal 8
And this is my code:
<?php
namespace Drupal\import_json_test\Plugin\rest\resource;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\node\Entity\Node;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Psr\Log\LoggerInterface;
/**
* Provides a resource to get view modes by entity and bundle.
*
* #RestResource(
* id = "tio_rest_json_source",
* label = #Translation("Tio rest json source"),
* serialization_class = "Drupal\node\Entity\Node",
* uri_paths = {
* "canonical" = "/api/custom/",
* "https://www.drupal.org/link-relations/create" = "/api/custom"
* }
* )
*/
class TioRestJsonSource extends ResourceBase {
/**
* A current user instance.
*
* #var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* Constructs a new TioRestJsonSource object.
*
* #param array $configuration
* A configuration array containing information about the plugin
instance.
* #param string $plugin_id
* The plugin_id for the plugin instance.
* #param mixed $plugin_definition
* The plugin implementation definition.
* #param array $serializer_formats
* The available serialization formats.
* #param \Psr\Log\LoggerInterface $logger
* A logger instance.
* #param \Drupal\Core\Session\AccountProxyInterface $current_user
* A current user instance.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
array $serializer_formats,
LoggerInterface $logger,
AccountProxyInterface $current_user) {
parent::__construct($configuration, $plugin_id,
$plugin_definition, $serializer_formats, $logger);
$this->currentUser = $current_user;
}
/**
* {#inheritdoc}
*/
public static function create(ContainerInterface $container, array
$configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->getParameter('serializer.formats'),
$container->get('logger.factory')->get('import_json_test'),
$container->get('current_user')
);
}
/**
* Responds to POST requests.
*
* Returns a list of bundles for specified entity.
*
* #param $data
*
* #param $node_type
*
* #return \Drupal\rest\ResourceResponse
*
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
* Throws exception expected.
*/
public function post($node_type, $data) {
// You must to implement the logic of your REST Resource here.
// Use current user after pass authentication to validate access.
if (!$this->currentUser->hasPermission('access content')) {
throw new AccessDeniedHttpException();
}
$node = Node::create(
array(
'type' => $node_type,
'title' => $data->title->value,
'body' => [
'summary' => '',
'value' => $data->body->value,
'format' => 'full_html',
],
)
);
$node->save();
return new ResourceResponse($node);
}
}
Now if i try to test this without passing a payload and modifing the return value in this way:
return new ResourceResponse(array('test'=>'OK'));
It's working!
But if i send a custom payload like this using my custom code above:
{
"title": [{
"value": "Test Article custom rest"
}],
"type": [{
"target_id": "article"
}],
"body": [{"value": "article test custom"}]
}
I recieve a 400 Error with: Symfony\Component\HttpKernel\Exception\BadRequestHttpException: The type link relation must be specified. in Drupal\rest\RequestHandler->handle() (line 103 of core/modules/rest/src/RequestHandler.php).
What's going Wrong?
Thx.
I have find a solution:
I have removed the annotation:
* serialization_class = "Drupal\node\Entity\Node",
Then i take care just for data in my post function:
/**
* Responds to POST requests.
*
* Returns a list of bundles for specified entity.
*
* #param $data
*
*
* #return \Drupal\rest\ResourceResponse
*
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
* Throws exception expected.
*/
public function post($data) {
// You must to implement the logic of your REST Resource here.
// Use current user after pass authentication to validate access.
if (!$this->currentUser->hasPermission('access content')) {
throw new AccessDeniedHttpException();
}
return new ResourceResponse(var_dump($data));
The important thing is, when you use postman for example, is to add an header with Content-Type -> application/json:
Instead of Content-Type -> application/hal+json
With this configuration i can post any type of JSON and then manage it as i prefer.
Bye!
I get the error when trying to make a post call to /api/subject/search
I assume it's a simple syntax error I'm missing
I have my api routes defined below
Route::group(array('prefix' => 'api'), function()
{
Route::post('resource/search', 'ResourceController');
Route::resource('resource', 'ResourceController');
Route::post('subject/search', 'SubjectController');
Route::resource('subject', 'SubjectController');
Route::resource('user', 'UserController');
Route::controller('/session', 'SessionController');
Route::post('/login', array('as' => 'session', 'uses' => 'SessionController#Store'));
});
And my controller is mostly empty
class SubjectController extends \BaseController
{
public function search()
{
$subjects = [];
if((int)Input::get('grade_id') < 13 && (int)Input::get('grade_id') > 8)
$subjects = Subject::where('name', 'like', '%HS%')->get();
else
$subjects = Subject::where('name', 'not like', '%HS%')->get();
return Response::json([
'success' => true,
'subjects' => $subjects->toArray()
]);
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
//
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
//
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id)
{
//
}
}
You need to specify the method.
try
Route::post('subject/search', 'SubjectController#search');
See the named route example:
Laravel Docs
In your case I think search is not resolved by the controller to load the search() method. You are also sending a POST for search functionality and I guess it's better to do a GET request since POST and PUT are for storing data.
Conventions
When creating API's it's a good thing to stick to naming conventions and patterns.
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
Solution
Your route could be simpler like this: api.yourdomain.com/api/subject?search=term1,term2. Doing this with a GET query makes it going to the index() method. There you can check the GET params and do your search stuff and return.
Check this for the cleanest and truely RESTful way to make an API in Laravel:
How do I create a RESTful API in Laravel to use in my BackboneJS app
I got same error when accessing object at index of an empty array in view blade php file.