Laravel, Reusable API Resource method - laravel

So I've created an API Resource and in the toArray() method, I've filtered which attributes get returned via query strings. I want to be able to do this for most of my API Resources so it makes sense to move this into a reusable method. What would be the best way of doing this?
I was thinking about extending the base Resource but I'm not sure how I would go about doing that. Or should it be moved into a service class or repository?
My method is below;
public function toArray($request)
{
if($request->fields) {
$fields = [];
$selectedFields = explode(',', $request->fields);
foreach($selectedFields as $field) {
if(isset($this->{$field})) {
$fields[$field] = $this->{$field};
}
}
return $fields;
} else {
return parent::toArray($request);
}
}
Ideally, I would like to do something like...
public function toArray($request)
{
if($request->fields) {
return parent::filterFields($request); // Not sure whether it should be parent::, $this or something else?
} else {
return parent::toArray($request); // Not sure whether it should be parent::, $this or something else?
}
// or event just
return parent::filterFields($request); // Not sure whether it should be parent::, $this or something else?
}
Full Classes:
ProjectResource.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class ProjectResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
dd(parent::filterFields());
// or
dd($this->filterFields());
if($request->fields) {
$fields = [];
$selectedFields = explode(',', $request->fields);
foreach($selectedFields as $field) {
if(isset($this->{$field})) {
$fields[$field] = $this->{$field};
}
}
return $fields;
} else {
return parent::toArray($request);
}
}
}
Test.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class Test extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function filterFields($request)
{
return 'test';
}
}
Thank you!

If you're extending the class, you can use either parent:: or $this->. The difference being parent will always refer to the parent class, $this will only refer to the parent if the method does not exist on the current class. Therefore, using $this allows extension of methods and properties.
Example:
<?php
class A {
public function test() {
echo 'A';
}
}
class B extends A {
public function test() {
echo 'B';
}
public function callParent(){
parent::test();
}
public function callThis() {
$this->test();
}
}
$b = new B();
$b->callParent(); // Echoes A
$b->callThis(); // Echos B
In your updated class representation, you have both classes extending Resource but ProjectResource will have no idea about the methods in Test. You would need ProjectResource to extend Test, which will also inherit methods from Resource since Test extends Resource (multiple inheritence).
Beyond that, how you implement is going to be opinion based and you have lots of options. You can extend an abstract class, you can use a dedicated filtering class, or you can use a trait.

You can extend the resource
<?php
namespace App\Http\Resources;
use App\Http\Resources\Product as ProductResource;
class ProductQuantity extends ProductResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$parent = parent::toArray($request);
return array_merge($parent, [
'depotsQuantity' => $this->warehouses->sum('pivot.quantity'),
]);
}
}

Related

Laravel Components not getting my methods in shared host

I've just migrated a project that was working great on my localhost to a shared hosting and my components suddently are not getting the methods that i gave them and i'm getting errors in my views like so :
Undefined variable: CatPromo
this is my Component :
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use App\Categories;
class promo extends Component
{
/**
* Create a new component instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\View\View|string
*/
public function render()
{
return view('components.promo');
}
public function CatPromo()
{
$Categories = Categories::all();
return $Categories;
}
}
Update : I removed the App\View\Components\promo.php to see if it can help me by throwing an error and it seems that he don't even detect the controller.
The documentation says: You should define the component's required data in its class constructor.
public function __construct($CatPromo)
{
// use as variable
$this->CatPromo = $CatPromo;
}
// use as method
public function CatPromo()
{
$Categories = Categories::all();
return $Categories;
}
And in blade template:
#foreach($CatPromo() as $key => $Categorie)

How do you override a class from the Laravel source code?

I need to make a change to the retrieveUser() function within Illuminate/Broadcasting/Broadcasters/Broadcaster.php.
The change works if I edit the class directly, but I have heard that you are not supposed to do that because it is difficult to track changes to the source code and because it will get overwritten when upgrading Laravel or when pushing to production.
So if I wanted to write my own modified retrieveUser() function for the Broadcaster class (it happens to be an abstract class which implements BroadcasterContract), then where and how would I do that?
Original function:
/**
* Retrieve the authenticated user using the configured guard (if any).
*
* #param \Illuminate\Http\Request $request
* #param string $channel
* #return mixed
*/
protected function retrieveUser($request, $channel)
{
$options = $this->retrieveChannelOptions($channel);
$guards = $options['guards'] ?? null;
if (is_null($guards)) {
return $request->user();
}
foreach (Arr::wrap($guards) as $guard) {
if ($user = $request->user($guard)) {
return $user;
}
}
}
New function:
protected function retrieveUser($request, $channel)
{
$options = $this->retrieveChannelOptions($channel);
$guards = $options['guards'] ?? null;
if (is_null($guards)) {
$token = $request->header('Token');
$id = Crypt::decrypt($token);
$user = User::find($id);
return $user;
}
foreach (Arr::wrap($guards) as $guard) {
if ($user = $request->user($guard)) {
return $user;
}
}
}
UPDATE
As #ggdx pointed out in the comments, I can override the class by doing class yourClass extends Illuminate\Broadcasting\Broadcasters\Broadcaster
However, I still don't know where to put this new class within the Laravel framework. I tried creating the new class in the /app route, but that did not work.
I'm not completely sure what you are trying to accomplish. But I think making a custom driver for a guard will do what you want. Looking at the docs https://laravel.com/docs/5.8/authentication#adding-custom-guards
You can do this in the boot method of your AuthServiceProvider.
Auth::viaRequest('custom-token', function ($request) {
return User::find(Crypt::decrypt($request->header('Token')));
});
Also, make sure to select it as the driver for your guard in your auth.php config file.

How to solve Class 'App\Http\Requests\Web\WebRequest' not found

I create a request in App\Http\Requests\Web in which it shows me the error.
Class 'App\Http\Requests\Web\WebRequest' not found
Here is the code of my Request CreateBucket.php:
<?php
namespace App\Http\Requests\Web;
class CreateBucket extends WebRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'bucket_name' => 'required|string|string|max:30',
'bucket_type' => 'required|string|string|max:30',
'bucket_image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg',
];
}
}
And Here is my code of Bucket Controller:
<?php
namespace App\Http\Controllers\Web;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\Web\CreateBucket;
use App\Bucket;
class BucketController extends Controller
{
public function index(Request $request)
{
$buckets = Bucket::orderBy('id','ASC')->paginate(10);
return view('buckets.index',compact('buckets',$buckets))
->with('i',($request->input('page',1) - 1) * 10);
}
public function create()
{
return view('buckets.create');
}
public function store(CreateBucket $request)
{
if($request->hasFile('bucket_image')) {
$bucket_image = $request->file('bucket_image');
$bucket_image_name = time().'.'.$bucket_image->getClientOriginalExtension();
$path = public_path('Storage/BucketImages');
$bucket_image->move($path, $bucket_image_name);
$bucket_image = 'Storage/BucketImages/'.$bucket_image_name;
} else {
$bucket_image = NULL;
}
$category = Category::create([
'bucket_name' => $request->input('bucket_name'),
'bucket_image'=> $bucket_image,
'bucket_type' => $request->input('bucket_type'),
]);
return redirect()->route('buckets.index')
->with('success','Bucket created successfully');
}
Please Help me to resolve this error. Thanks.
My WebRequest.php is missing in Requests folder that why he gave me this Error.
Here is the WebRequest.php file I created and my issue is resolve.
<?php
namespace App\Http\Requests\Web;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
class WebRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}

Laravel Relationship Find UUID

I have make a Trait for UUID. I use a lot of relationschip inside my code. On a relationship you can do find() and findOrFail() but i have write a code for findU() and findUOrFail() but i can't use it inside a relationship. How can i fix it?
Trait:
<?php
namespace App\Modules\Base\Traits;
use Ramsey\Uuid\Uuid;
/**
* Trait Uuids
*
* #package Modules\Core\Traits
*/
trait Uuids
{
/**
* Boot function from laravel.
*/
public static function bootUuids ()
{
static::creating(function ($model) {
$model->uuid = Uuid::uuid4()->toString();
});
}
/**
* #param $uuid
*
* #return mixed
*/
public static function findU ($uuid)
{
return static::where('uuid', '=', $uuid)->first();
}
/**
* #param $uuid
*
* #return mixed
*/
public static function findUOrFail($uuid)
{
$post = static::where('uuid', '=', $uuid)->first();
if( is_null($post) ) {
return abort(404);
} else {
return $post;
}
}
}
Controller:
/**
* Show
*/
public function show(Request $request, $uuid)
{
return responder()->success($request->user()->projects()->findUOrFail($uuid))->respond();
}
Error:
Call to undefined method Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany::findUOrFail()
Assuming you don't need id since you're using uuid
In your migration file you need:
$table->uuid('uuid');
$table->primary('uuid');
In your model:
use Uuids;
protected $primaryKey = 'uuid';
public $incrementing = false;
Or much easier
In your migration file:
$table->uuid('id');
$table->primary('id');
In your model:
use Uuids;
public $incrementing = false;
You don't need to override findOrFail or find
It should help to have the function referenced directly in the model rather than trying to access it directly in a trait. I am assuming that you are including the Uuids trait above in your projects model. If so, try creating a method on the projects model like this:
public function tryFindUOrFail($uuid)
{
return $this->findUOrFail($uuid);
}
Then you would write your show method as:
return responder()->success($request->user()->projects()->tryFindUOrFail($uuid))->respond();
If this doesn't work, you may need to include your method with the $appends array so that it is directly accessible through the relationship.

Extra data on a collection operation

Does anybody know how to add extra data on a collection?
The doc says much about how to add extra data on an item which translates into decorating the ItemNormalizer service, and it works pretty well.
But I’m struggling in finding out which normalizer to decorate when it comes to add some data on a collection of entities. The extra data could be anything: the current user logged in, a detailed pager, some debug parameters, ... that are not related to a specific entity, but rather on the request itself.
The only working solution for now is to hook on a Kernel event but that's definitely not the code I like to write:
use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class SerializeListener implements EventSubscriberInterface
{
/**
* #var Security
*/
private $security;
/**
* #var NormalizerInterface
*/
private $normalizer;
public function __construct(
Security $security,
NormalizerInterface $normalizer
) {
$this->security = $security;
$this->normalizer = $normalizer;
}
public function addCurrentUser(GetResponseForControllerResultEvent $event)
{
$request = $event->getRequest();
if ($request->attributes->has('_api_respond')) {
$serialized = $event->getControllerResult();
$data = json_decode($serialized, true);
$data['hydra:user'] = $this->normalizer->normalize(
$this->security->getUser(),
$request->attributes->get('_format'),
$request->attributes->get('_api_normalization_context')
);
$event->setControllerResult(json_encode($data));
}
}
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
'addCurrentUser',
EventPriorities::POST_SERIALIZE,
],
];
}
}
Any ideas?
Thank you,
Ben
Alright, I finally managed to do this.
namespace App\Api;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class ApiCollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
/**
* #var NormalizerInterface|NormalizerAwareInterface
*/
private $decorated;
public function __construct(NormalizerInterface $decorated)
{
if (!$decorated instanceof NormalizerAwareInterface) {
throw new \InvalidArgumentException(
sprintf('The decorated normalizer must implement the %s.', NormalizerAwareInterface::class)
);
}
$this->decorated = $decorated;
}
/**
* #inheritdoc
*/
public function normalize($object, $format = null, array $context = [])
{
$data = $this->decorated->normalize($object, $format, $context);
if ('collection' === $context['operation_type'] && 'get' === $context['collection_operation_name']) {
$data['hydra:meta'] = ['foo' => 'bar'];
}
return $data;
}
/**
* #inheritdoc
*/
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
/**
* #inheritdoc
*/
public function setNormalizer(NormalizerInterface $normalizer)
{
$this->decorated->setNormalizer($normalizer);
}
}
# config/services.yaml
services:
App\Api\ApiCollectionNormalizer:
decorates: 'api_platform.hydra.normalizer.collection'
arguments: [ '#App\Api\ApiCollectionNormalizer.inner' ]
Keep it for the records :)

Resources