Deleting Data with Relation using REST API | Laravel - laravel

First of all I have Book DB field and BookReview DB field
Book Model:
public function reviews()
{
return $this->hasMany(BookReview::class);
}
BookReview Model:
public function book()
{
return $this->belongsTo(Book::class);
}
What I need to do is to delete a BooksReview, but it consists in a relationship with Book field
This is the route
Route::delete('/books/{bookId}/reviews/{reviewId}');
And this is the controller:
public function destroy(int $bookId, int $reviewId, Request $request)
{
// TODO: implement
$check_bookReview = BookReview::firstWhere('id', $reviewId);
if ($check_bookReview) {
BookReview::destroy($id);
return response()->noContent();
} else {
abort(404);
}
}
I've never tried to delete data with a relation before, how do I accomplish this?
Thank you in advance

You can leverage Route Model Binding to have Laravel automatically attempt to find and return the relevant models from your database based on the URI parameters defined in your route and associated function:
Route::delete('/books/{bookId}/reviews/{reviewId}', [BookReviewController::class, 'destroy');
public function destroy(Request $requst, Book $bookId, BookReview $reviewId)
{
$bookId->delete();
return response()->json([], 204);
}
Laravel will automatically attempt to find and return the relevant Book and BookReview models from the database based on the value of each parameter. If it can't find the relevant model it will fail with a 404 but otherwise, the value of each parameter will be that of a model. Note that the name of the parameters defined in the Route and function definitions must match for route model binding to work.

Instead of:❌
public function destroy(int $bookId, int $reviewId, Request $request)
{
// #TODO implement
$check_bookReview = BookReview::firstWhere('id', $reviewId);
if ($check_bookReview) {
BookReview::destroy($id);
return response()->noContent();
}
else {
abort(404);
}
}
Use this: ✅
public function destroy(int $bookId, int $reviewId, Request $request)
{
if (BookReview::destroy($reviewId)) {
return response()->noContent();
} else {
abort(404);
}
}
\Illuminate\Database\Eloquent\Model::destroy($ids) returns a total count of destroyed models for the given IDs.

Related

How to get multiple relationships Data in My Laravel Vue Project

I have a 3 model "User","Review","ReviewReplay"
in user Model
public function reviews()
{
return $this->hasMany('App\Models\Review');
}
public function reviewReplys()
{
return $this->hasMany('App\Models\ReviewReply');
}
In Review Model
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function reviewReplys()
{
return $this->hasMany('App\Models\ReviewReply');
}
in Review Replay Model
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function review()
{
return $this->belongsTo('App\Models\Reviews');
}
in My Controller
public function getReview($id)
{
$review= Review::where('product_id',$id)->with('user','reviewReplys')->paginate(10);
return response()->json($review);
}
Now here I run Review foreach loop and here I need review.reviewReplay.user information. How I Get The Review Replay User information inside Review Foreach loop.
You can do it as below.
$review= Review::where('product_id',$id)->with(['reviewReplys.user'])->paginate(10);
with(['reviewReplys.user']) --> it will make connection between Review model to reviewReplys model +
reviewReplys model to User model

Getting extra fields from laravel api having belongstomany relationship

I have two data tables related to each other by the belongstomany relationship. And when I am fetching data from its api controllers with selecting only two column keys ['id','title'] yet it returns some extra data in the response object.
modelcode:
public function place(){
return $this->belongsToMany(Place::class,'city_place')->select(array('id', 'title'));
}
controller code:
public function ofcity($id)
{
$city=City::findOrFail($id);
return new CityResource( $city->place()->get());
}
enter image description here
You must indicate the name of the table in front of the fields.
model Place code:
protected $columns = ['places.id', 'places.title']; //all column for select
public function scopeExclude($query, $value = [])
{
return $query->select(\array_diff($this->columns, (array) $value));
}
model City code:
public function place()
{
return $this->belongsToMany(Place::class,'city_place', 'city_id', 'place_id');
}
controller code:
public function ofcity($id)
{
$cities = City::findOrFail($id)->place()->exclude(['featured_image'])->get()->toArray();
return response()->json(['cities' => $cities], 200);
}
In exclude skip all the fields that need not to be shown.
Thanks everyone here helping me out but none of the above solution worked..I figured it out after trying different functions and spending hours on this.
model Place code:
public function place(){
return $this->belongsToMany(Place::class,'city_place','city_id','place_id')->select(array('places.id', 'places.title'));
}
controller code:
public function ofcity($id)
{
$city=City::findOrFail($id);
return new CityResource( $city->place()->get()->map(function ($item,$key) {
return ['id' => $item['id'],'title'=>$item['title']];
})
);

Laravel Model must return a relationship instance

My website has comments. These comments can have "votes", upvotes and downvotes.
I have a Comment Model and a CommentVote model.
In my comment model I have a functions that returns the votes:
public function votes() {
return $this->hasMany('App\CommentVote', 'comment_id');
}
public function upvotes() {
return $this->hasMany('App\CommentVote', 'comment_id')->where('vote', 1);
}
public function downvotes() {
return $this->hasMany('App\CommentVote', 'comment_id')->where('vote', -1);
}
Notice that upvotes are stored in the database in a tinyInt as 1 and downvotes are stored as -1
In my CommentVote model I have the belongsTo relationship:
public function comment() {
return $this->belongsTo('App\Comment');
}
Now I want to have a function that calculates the total "score" of the comment. Total upvotes minus total downvotes.
I try to make a function that counts all the upvotes - all the downvotes.
public function score() {
return $this->upvotes()->count() - $this->downvotes()->count();
}
This returns the error:
App\Comment::score must return a relationship instance.
In fact using the count() anywhere will return this error, despite it working fine in my other Models.
Doing something simple like:
public function voteCount() {
return $this->hasMany('App\CommentVote', 'comment_id')->count();
or even
return $this->votes()->count();
}
will return the error:
App\Comment::voteCount must return a relationship instance.
Why is this happening?
EDIT:
Here is the controller, as per requests in the comments:
public function getSubmission($subchan, $id, $URLtitle) {
$submission = Submission::where('id', $id)->first();
$comments = Comment::where('submission_id', $submission->id)->where('parent_id', NULL)->orderBy('created_at', 'desc')->get();
$comments = $comments->sortByDesc(function($comment){
return count($comment['upvotes']) - count($comment['downvotes']);
});
if (!$submission) {
return redirect()->route('home')->with('error', 'Submission not found.' );
}
return view('submissions.submission')
->with('submission', $submission)
->with('navSubchan', $submission->getSubchan->name)
->with('submissionPage', 1)
->with('comments', $comments)
;
}
I suspect you're doing $model->score, which is going to look for a function called score(), but in a specific manner that expects that function to return a HasMany, HasOne, BelongsTo etc. style relationship object.
Consider an accessor function instead.
public function getScoreAttribute() {
return $this->upvotes()->count() - $this->downvotes()->count();
}
allows you to do $model->score successfully.

How to filter a polymorphic relationship to only return a specific model?

I'm having an issue with defining a function to filter a polymorphic relationship and only return a specific model. I'll try to explain below:
Say I have three models: A, B, and C. Model A can belong to either of the other two models. Say we're using the polymorphism field name of recipient, so on our model A database schema we have recipient_type and recipient_id.
On model A, I have a the default function called recipient, defined like so:
public function recipient()
{
return $this->morphTo();
}
However, I want a function called b() which will return a relationship so that it can be used with a query builder using the with() function. The idea being I can call $a->b and it will either return an instance of B or null, depending on whether the instance of A belongs to and instance of B...
Sorry this has been a bit of a mouthful..
Appreciate all the help I can get with this one!
Cheers!
You can define it like this
Model A (define accessor)
public function recipient()
{
return $this->morphTo();
}
public function bRelation()
{
return $this->belongsTo(B::class, 'recipient_id', 'id');
}
public function cRelation()
{
return $this->belongsTo(C::class, 'recipient_id', 'id');
}
public function getBAttribute(){ //define accessor
if($this->recipient_type == 'App\B') return $this->bRelation;
return null;
}
public function getCAttribute(){ //define accessor
if($this->recipient_type == 'App\C') return $this->cRelation;
return null;
}
Now use it with eager loading
$records = A::with('bRelation', 'cRelation')->get();
foreach($records as $a){
dd($a->b); //it will return you either instance of `B` or `null`
}
Works well with accessors:
public function getIssueAttribute()
{
if($this->commentable_type == 'issues') return $this->commentable;
return null;
}
public function getProjectAttribute()
{
if($this->commentable_type == 'projects') return $this->commentable;
return null;
}
public function commentable()
{
return $this->morphTo('commentable');
}

Eloquent relationships for not-existing model class

I would like to have in my applications many models/modules but some of them would be removed for some clients.
Now I have such relation:
public function people()
{
return $this->hasMany('People', 'model_id');
}
and when I run $model = Model::with('people')->get(); it is working fine
But what if the People model doesn't exist?
At the moment I'm getting:
1/1 ErrorException in ClassLoader.php line 386: include(...): failed
to open stream: No such file or directory
I tried with
public function people()
{
try {
return $this->hasMany('People', 'model_id');
}
catch (FatalErrorException $e) {
return null;
}
}
or with:
public function people()
{
return null; // here I could add checking if there is a Model class and if not return null
}
but when using such method $model = Model::with('people')->get(); doesn't work.
I will have a dozens of relations and I cannot have list of them to use in with. The best method for that would be using some empty relation (returning null) just to make Eloquent not to do anything but in this case Eloquent still tries to make it work and I will get:
Whoops, looks like something went wrong.
1/1 FatalErrorException in Builder.php line 430: Call to a member function
addEagerConstraints() on null
Is there any simple solution for that?
The only solution I could come up with is creating your own Eloquent\Builder class.
I've called it MyBuilder. Let's first make sure it gets actually used. In your model (preferably a Base Model) add this newEloquentBuilder method:
public function newEloquentBuilder($query)
{
return new MyBuilder($query);
}
In the custom Builder class we will override the loadRelation method and add an if null check right before addEagerConstraints is called on the relation (or in your case on null)
class MyBuilder extends \Illuminate\Database\Eloquent\Builder {
protected function loadRelation(array $models, $name, Closure $constraints)
{
$relation = $this->getRelation($name);
if($relation == null){
return $models;
}
$relation->addEagerConstraints($models);
call_user_func($constraints, $relation);
$models = $relation->initRelation($models, $name);
$results = $relation->getEager();
return $relation->match($models, $results, $name);
}
}
The rest of the function is basically the identical code from the original builder (Illuminate\Database\Eloquent\Builder)
Now simply add something like this in your relation function and it should all work:
public function people()
{
if(!class_exist('People')){
return null;
}
return $this->hasMany('People', 'model_id');
}
Update: Use it like a relationship
If you want to use it like you can with a relationship it gets a bit more tricky.
You have to override the getRelationshipFromMethod function in Eloquent\Model. So let's create a Base Model (Your model obviously needs to extend it then...)
class BaseModel extends \Illuminate\Database\Eloquent\Model {
protected function getRelationshipFromMethod($key, $camelKey)
{
$relations = $this->$camelKey();
if ( $relations instanceof \Illuminate\Database\Eloquent\Collection){
// "fake" relationship
return $this->relations[$key] = $relations;
}
if ( ! $relations instanceof Relation)
{
throw new LogicException('Relationship method must return an object of type '
. 'Illuminate\Database\Eloquent\Relations\Relation');
}
return $this->relations[$key] = $relations->getResults();
}
}
Now we need to modify the relation to return an empty collection
public function people()
{
if(!class_exist('People')){
return new \Illuminate\Database\Eloquent\Collection();
}
return $this->hasMany('People', 'model_id');
}
And change the loadRelation function in MyBuilder to check for the type collection instead of null
protected function loadRelation(array $models, $name, Closure $constraints)
{
$relation = $this->getRelation($name);
if($relation instanceof \Illuminate\Database\Eloquent\Collection){
return $models;
}
// ...
}

Resources