Eloquent all and custom property - php

Let's say, I have an Order model and I have an endpoint, which querys all the orders.
Now it's something like this:
public function findAll(Request $request, Response $response, $args)
{
return Order::all()->toJson(JSON_PRETTY_PRINT);
}
But, I want to add custom properties to the json response.
My order looks like this:
protected $fillable = [
'productIds',
'fullname',
'phone',
'location'
];
And I want to add a 'names' and a 'totalPrice' property, which are not in the DB, they are calculated by a getProductNameList() and a getSumPrice() method. How is that possible? :)

You could try to add the data directly from the model using the $append model attribute like this:
Class Order extends Model
{
protected $append = ['attribute_name'];
public function getAttributeName() {
$dataToReturn = [];
return ($dataToReturn);
}
}
Doc here : https://laravel.com/docs/5.4/eloquent-serialization#appending-values-to-json

you can use json response so you can pass custom array
public function findAll(Request $request, Response $response, $args)
{
$order= Order::all();
return response()->json([
'order' => $order,
'names' => 'CA',
'totalPrice'=>150
]);
}
As per your question you are using separate methods for calculating so getProductNameList() and a getSumPrice() .In both method you can return $value and method response you can pass it to this array
For more ref:
https://laravel.com/docs/5.5/responses#json-responses

Related

Create Model with HasMany relation using Repository Pattern

I'm trying to implement the repository pattern and save a relationship using the create method as shown below.
abstract class EloquentRepository implements Repository {
public function create($data)
{
return $this->model->create($data);
}
}
Within my controller I have injected the repository:
public function __construct(SubscriberRepository $subscriberRepository,
SubscribableRepository $subscribableRepository)
{
$this->subscriberRepository = $subscriberRepository;
$this->subscribableRepository = $subscribableRepository;
}
My store method looks like:
public function store(CreateSubscriberRequest $request): JsonResponse
{
$subscribable = $this->subscribableRepository->findByIdentifier($request->input('type'))
->firstOrFail();
$attributes = [
'name' => $request->input('name'),
'email' => $request->input('email')
];
$subscriber = $this->subscriberRepository->create($attributes);
}
Subscriber Model
public function subscribable()
{
return $this->belongsTo(Subscribable::class, 'subscribable_id');
}
Subscribable Model
public function subscribers()
{
return $this->hasMany(Subscriber::class);
}
My issue General error: 1364 Field 'subscribable_id' doesn't have a default value is because the subscribable_id is a foreign key and not set in the create method.
How do I relate the subscribable model, setting the subscribable_id? I don't think setting the subscribable_id in the fillable property is the way to go with this.
Many thanks in advance.
Laravel gives functionality to save relations using the related model instances.
So You can save relation by calling create method on relation like this:
public function store(CreateSubscriberRequest $request): JsonResponse
{
$subscribable = $this->subscribableRepository->findByIdentifier($request->input('type'))
->firstOrFail();
$attributes = [
'name' => $request->input('name'),
'email' => $request->input('email')
];
$subscribable->subscribers()->create($attributes);
}
See laravel doc on relationship

Adding filter to eloquent resource to attach relationship conditionally

Laravel 5.8
PHP 7.4
I want to load the relationships conditionally like
http://127.0.0.1:8000/api/posts
and
http://127.0.0.1:8000/api/posts/1 are my end points now, I want to load comments like
http://127.0.0.1:8000/api/posts/?include=comments and
http://127.0.0.1:8000/api/posts/1/?include=comments
If the query parameter is there, only then it should load comments with posts or it should load only posts/post
I am doing this by referring a blog post
now, RequestQueryFilter
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
class RequestQueryFilter
{
public function attach($resource, Request $request = null)
{
$request = $request ?? request();
return tap($resource, function($resource) use($request) {
$this->getRequestIncludes($request)->each(function($include) use($resource) {
$resource->load($include);
});
});
}
protected function getRequestIncludes(Request $request)
{
// return collect(data_get($request->input(), 'include', [])); //single relationship
return collect(array_map('trim', explode(',', data_get($request->input(), 'include', [])))); //multiple relationships
}
}
and in helper
<?php
if ( ! function_exists('filter') ) {
function filter($attach)
{
return app('filter')->attach($attach);
}
}
?>
in PostController
public funciton show(Request $request, Post $post) {
return new PostResource(filter($post));
}
but when I am trying to retrieve
http://127.0.0.1:8000/api/posts/1/?include=comments getting no comments, with no error in log
A work around will be PostResource
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'name' => $this->title,
'body' => $this->content,
];
$filter = $request->query->get('include', '');
if($filter){
$data[$filter] = $this->resource->$filter;
}
return $data;
}
I want to load the relationships conditionally like
Lazy Eager Loading using the load() call
The Lazy Eager Loading accomplishes the same end results as with() in Laravel, however, not automatically. For example:
?include=comments
// Get all posts.
$posts = Post::without('comments')->all();
if (request('include') == 'comments')) {
$posts->load('comments');
}
return PostResource::collection($posts);
Alternativelly, you could require the include query string to be an array:
?include[]=comments&include[]=tags
// Validate the names against a set of allowed names beforehand, so there's no error.
$posts = Post::without(request('includes'))->all();
foreach (request('includes') as $include) {
$posts->load($include);
}
return PostResource::collection($posts);
The call without() is only required in case you defined your model to automatically eager load the relationships you want to conditionally load.
With all data filtered in Controller, just make sure to display only loaded relations in your PostResource
public function toArray($request) {
$data = [...];
foreach ($this->relations as $name => $relation)
{
$data[$name] = $relation;
}
return $data;
}
I would create a custom resource for the posts with
php artisan make_resource
command.
E.g. PostResource.
The toArray function of the resource must return the data.
PostResource.php
public function toArray($request){
$data =['title' => $this->resource->title,
'body' => $this->resource->body,
'images' => new ImageCollection($this->whenLoaded('images')),
];
$filter = $request->query->get('filter', '');
if($filter){
$data['comments'] => new CommentCollection($this->resource->comments);
}
return $data;
}
Also, for collections, you need to create a ResourceCollection.
PostResourceCollection.php
class PostResourceCollection extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
];
}
}
In your controller:
PostsController.php
//show one post
public function show(Post $post, Request $request)
{
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResource($post));
}
//list of posts
public function index(Request $request)
{
$posts = Post::all();
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResourceCollection($posts));
}
Partial Solution
It will need a small change in resource class
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'comments' => new CommentCollection($this->whenLoaded('comments')),
'images' => new ImageCollection($this->whenLoaded('images')),
];
return $data;
}
and it will load comments and images if loaded and that depends on the include query parameter, if that is not included, it will not load the relationship.
However,
In post collection
return [
'data' => $this->collection->transform(function($post){
return [
'id' => $post->id,
'title' => $post->title,
'body' => $post->body,
'comments' => new CommentCollection($post->whenLoaded('comments')),
'images' => new ImageCollection($post->whenLoaded('images')),
];
}),
];
will results in
"Call to undefined method App\Models\Customer::whenLoaded()",, if anyone suggests a complete solution, it will be a great help, if I will able to do, it I will update here.

Laravel : how to validate a form field according to possible values of a Model's attribute?

I'm performing validation of a form, where a user may select a range of values (based on a set of entries in a model)
E.g. I have the Model CfgLocale(id, name)
I would like to have something like:
CfgLocale->listofAvailableIds() : return a array
What I did is:
Inside Model this method:
class CfgLocale extends Model
{
protected $table = 'cfg_locales';
public static function availableid()
{
$id_list = [];
$res = self::select('id')->get();
foreach($res as $i){
$id_list[] = $i->id;
}
return $id_list;
}
}
On Controller for validation I would do then:
$this->validate($request, [
'id' => 'required|integer|min:1',
...
'locale' => 'required|in:'.implode(',', CfgLocale::availableid()),
]);
Any better Idea, or Laravel standard to have this done?
Thanks
You can use exists rule of laravel.You can define a validation rule as below. Might be this can help.
'locale' => 'exists:cfg_locales,id'
Use this code instead,
class CfgLocale extends Model
{
protected $table = 'cfg_locales';
public static function availableid()
{
return $this->pluck('id')->toArray();
}
}
pluck method selects the id column from your table and toArray method converts your model object collection into array.
Know more about Laravel Collections here.
This will return an array of IDs:
public static function availableid()
{
return $this->pluck('id')->toArray();
}
https://laravel.com/docs/5.3/collections#method-pluck
https://laravel.com/docs/5.3/collections#method-toarray

Laravel 5.2 - Using SetIdAttribute() Mutator To Set Other Value

I am currently creating a blog where each Post row in my database will have a unique hash attribute that is based of the post's id (incrementing, always unique).
This my Post model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Hashids;
class Post extends Model
{
public function setTitleAttribute($value)
{
$this->attributes['title'] = $value;
if (! $this->exists) {
$this->attributes['slug'] = str_slug($value);
}
}
public function setIdAttribute($value) {
$this->attributes['id'] = $value;
$this->attributes['hash'] = Hashids::encode($value);
}
}
When I run this factory
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'title' => $faker->sentence(mt_rand(3, 10)),
'content' => join("\n\n", $faker->paragraphs(mt_rand(3, 6))),
'author' => $faker->name,
'category' => rand(1, 20),
];
});
The setIdAttribute($value) function is getting called, but my hash attribute is not being set. I am not sure if it is getting overwritten or what.
If I move the line
$this->attributes['hash'] = Hashids::encode($value);
to the function
public function setTitleAttribute($value)
and encode the title attribute it works fine, but I want to encode the 'id' attribute. Any idea how I would do this?
You can add the following to your model:
/**
* Events
*/
public static function boot()
{
parent::boot();
static::created(function($model)
{
$model->hash = Hashids::encode($model->id);
$model->slug = str_slug($model->title);
}
}
It's likely setIdAttribute($value) isn't being called until after the insert runs because it doesn't know the ID until then.
The real issue is you can't set a hash of the id in the same query because the id isn't going to be known (assuming it's auto_incrementing) until after the insert.
Because of this, the best you can probably do here is fire some code on the model's saved event.
In that model, you can probably do something like...
public static function boot()
{
parent::boot();
static::flushEventListeners(); // Without this I think we have an infinite loop
static::saved(function($post) {
$post->hash = Hashids:encode($post->id);
$post->save();
});
}

Laravel save many-to-many relationship in Eloquent mutators

I've got 2 models with a many-to-many relationship. I want to be able to set a specific attribute with an array of ids and make the relationship in the mutator like this:
<?php
class Profile extends Eloquent {
protected $fillable = [ 'name', 'photo', 'tags' ];
protected $appends = [ 'tags' ];
public function getTagsAttribute()
{
$tag_ids = [];
$tags = $this->tags()->get([ 'tag_id' ]);
foreach ($tags as $tag) {
$tag_ids[] = $tag->tag_id;
}
return $tag_ids;
}
public function setTagsAttribute($tag_ids)
{
foreach ($tag_ids as $tag_id) {
$this->tags()->attach($tag_id);
}
}
public function tags()
{
return $this->belongsToMany('Tag');
}
}
<?php
class Tag extends Eloquent {
protected $fillable = [ 'title' ];
protected $appends = [ 'profiles' ];
public function getProfilesAttribute()
{
$profile_ids = [];
$profiles = $this->profiles()->get([ 'profile_id' ]);
foreach ($profiles as $profile) {
$profile_ids[] = $profile->profile_id;
}
return $profile_ids;
}
public function profiles()
{
return $this->belongsToMany('Profile');
}
}
However the setTagsAttribute function isn't working as expected. I'm getting the following error: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'profile_id' cannot be null (SQL: insert intoprofile_tag(profile_id,tag_id) values (?, ?)) (Bindings: array ( 0 => NULL, 1 => 1, ))
You can't attach many-to-many relations until you've saved the model. Call save() on the model before setting $model->tags and you should be OK. The reason for this is that the model needs to have an ID that Laravel can put in the pivot table, which needs the ID of both models.
It looks like you're calling the function incorrectly or from an uninitialized model. The error says that profile_id is NULL. So if you're calling the function as $profile->setTagsAttribute() you need to make sure that $profile is initialized in the database with an ID.
$profile = new Profile;
//will fail because $profile->id is NULL
//INSERT: profile->save() or Profile::Create();
$profile->setTagsAttribute(array(1,2,3));
Additionally, you can pass an array to the attach function to attach multiple models at once, like so:
$this->tags()->attach($tag_ids);
You can also pass it the model instead of the ID (but pretty sure array of models won't work)
Try using the sync method:
class Profile extends Eloquent {
protected $fillable = [ 'name', 'photo', 'tags' ];
protected $appends = [ 'tags' ];
public function getTagsAttribute()
{
return $this->tags()->lists('tag_id');
}
public function setTagsAttribute($tag_ids)
{
$this->tags()->sync($tagIds, false);
// false tells sync not to remove tags whose id's you don't pass.
// remove it all together if that is desired.
}
public function tags()
{
return $this->belongsToMany('Tag');
}
}
Don't access the tags through the tags() function, rather use the tags property. Use the function name if you want to pop additional parameters onto the relationship query and the property if you just want to grab the tags. tags() works in your getter because you're using get() on the end.
public function setTagsAttribute($tagIds)
{
foreach ($tagIds as $tagId)
{
$this->tags->attach($tagId);
}
}

Categories