Extract mentioned users in post like Facebook comment - laravel

I followed this Laracast tutorial - https://laracasts.com/series/lets-build-a-forum-with-laravel/episodes/56 - to get "mentions" inside body. However I now want to fetch all the data for a thread, so I want all posts, and all related users, specifically mentioned users.
I am fetching all posts like this:
public function get(Thread $thread) {
return $thread->with(['posts'=>function($query) { return $query->with('user'); }]);
}
This returns to me data like this:
{
"id": 1,
"posts": [
{
"id": 1,
"user_id": 13,
"body": "Hi #14 and #15",
"user": {
"id": 13,
"name": "Joe"
}
},
{
"id": 2,
"user_id": 14,
"body": "Hi back at you"
"user": {
"id": 14,
"name": "Bob"
}
}
]
}
We see I get the author user's just fine. But I also need the mentioend users.
We see in the first one, during the store process, I converted #string to #USER_ID.
I did this in the store:
preg_match_all('/\#[^\s\.]+)/', $post->body, $matches);
And repalced with user_id.
Now however on fetch, I want to extract the user_id's from the post and attach to the resulting data these users. Is this possible?
My goal:
{
"id": 1,
"posts": [
{
"id": 1,
"user_id": 13,
"body": "Hi #14 and #15",
"users": [
{
"id": 13,
"name": "Joe"
},
{
"id": 14,
"name": "Bob"
},
{
"id": 15,
"name": "Ryan"
}
},
{
"id": 2,
"user_id": 14,
"body": "Hi back at you",
}
]
}
Is there a way to run the regex on each body and select the mentioned users too?

I think your goal isn't the correct one. You should think a good database design.
In your case, It's good to have mentions relation in your post (not users). Then you can attach the mentioned users to the post mentions. Something like,
preg_match_all('/\#[^\s\.]+)/', $post->body, $matches);
// get the author of the post
$author = $request->user();
// get all the user account for every #, except the author itself
$mentions = User::where(function ($query) use ($matches) {
foreach ($matches as $item) {
$query->orWhere('username', 'LIKE', $item);
}
})->where('id', '!=', $author->id)->get();
// attach the author to the post
$post->user()->save($author);
// attach $mentions to the post
$post->mentions()->saveMany($mentions);
// notify user were mentioned
Notification::send($mentions, new YouWereMentioned($post));
then to fetch the posts, you can do like so,
public function get(Thread $thread) {
return $thread->with([
'posts'=> function($query) {
return $query->with('user', 'mentions');
}
]);
}
Note. The laracasts video is only for the subscriber. You shouldn't make a question using a private resource. Or you must explain it all.

Related

Laravel How to orderby()->first() in eager loading

I have this query which is working just fine except when i call ->first() function on it, the data from certain relations disappear.
What I am trying to achieve:
I am trying to get the sp_services where price is lowest. The first object (id: 1) in data array has two sp_services, the second object inside data array (id: 13) has only one sp_services. When i do $query->orderBy('sp_services.price', 'DESC')->first(); The object from first one is there but the second one where (id: 6, name: car wash) disappears and there is only empty array.
In the below mentioned JSON I'm only showing the relevant data here. Can anyone please tell me what am i doing wrong here ? and why is it not working. Also is there a better way to achieve desired result.
Query:
$branchesQuery = SpBranch::query();
$branchesQuery->where('status', true);
$branchesQuery->when($request->filled('price'), function($query) use($request) {
return $query->with(['service_provider' => function($query) use($request) {
$query->whereHas('sp_services')
->with(['sp_services' => function($query) use($request) {
$query->orderBy('sp_services.price', 'DESC'); //->first();
}]);
}]);
});
$branches = $branchesQuery->paginate($perPageLimit);
return $branches;
Models:
class SpBranch extends Model
{
public function service_provider()
{
return $this->belongsTo(ServiceProvider::class, 'sp_id');
}
}
class ServiceProvider extends Model
{
public function sp_services()
{
return $this->hasMany(SpService::class, 'sp_id', 'id');
}
}
JSON:
{
"data": [
{
"id": 1,
"sp_id": 1,
"name": "Branch 01",
"status": 1,
"service_provider": {
"id": 1,
"name": "Service Provider 01",
"sp_services": [
{
"id": 3,
"sp_id": 1,
"branch_id": null,
"name": "Service 03",
"price": 50
},
{
"id": 4,
"sp_id": 1,
"branch_id": null,
"name": "Service 04",
"price": 70
}
]
}
},
{
"id": 13,
"sp_id": 5,
"name": "Branch 13",
"status": 1,
"service_provider": {
"id": 5,
"name": "Service Provider 05",
"sp_services": [
{
"id": 6,
"sp_id": 5,
"branch_id": null,
"name": "car wash",
"price": 300
}
]
}
}
]
}
You cannot order or group by a eager loaded table on database, because what WITH method does is that it fires a new query meaning that is not connected to the first one at all, what you can do is you can order or group them in collections meaning after the data is fetch from db you can order them, I think function is called ->sortBy() which will order by the field you want, but it will cost you on performance. If performance is important in your case, you can always use joins to join both tables which will enable you order results directly from database.
class ServiceProvider extends Model
{
public function sp_services()
{
return $this->hasMany(SpService::class, 'sp_id', 'id')->orderBy('price', 'asc');
}
}

How to get an array from a single field from related model in laravel?

The following:
Customer::with('tickets:customer_id,subject')->get();
returns the following:
[
{
"id": 1,
"company": "Test Ltd",
"support": "Standard",
"tickets": [
{
"customer_id": "1",
"name": "Test ticket 1"
},
{
"customer_id": "1",
"name": "Test ticket 2"
}
]
}
]
How can I flatten the related field, tickets to get:
tickets: ["Test ticket 1", "Test ticket 2"]
I tried doing a ->flatten() after ->get() but that didn't work at all.
It feels like it should be something simple that I'm missing and just can't find in the docs or by googling (thanks for the word with, that screws pretty much all sane results).
I haven't tested this out but using ->map() might help you in this instance:
$customers = Customer::with('tickets:customer_id,name')
->get()
->map(function($customer, $key) {
$customer->tickets = $customer->tickets->pluck('name')->all();
return $customer;
})->all();
->pluck() will return only the value of the key specified.
Update
This can be achieved by running a query within map and avoiding eager loading:
$customers = Customer::all()
->map(function($customer, $key) {
$customer['tickets'] = $customer->tickets()->get()->pluck('name')->all();
return $customer;
})->all();

relationship of an instantiated class - Laravel/Eloquent

I'm working on an api for a website, where I have the tables of blogs and categories
every blog belongs to category
and each category has many blogs
OK?
So, I created a BlogController and configured the routes to access the corresponding function.
/api/blog/ url via GET is redirected to the index function of my controller, and the function looks like this:
public function index()
{
$blog = Blog::with('category')->get();
return response()->json($blog, 200);
}
which returns this (correct)
[
{
"id": 1,
"title": "title from blog",
"body": "texto do meu blog",
"category_id": 1,
"created_at": "2018-09-05 21:08:21",
"updated_at": "2018-09-05 21:08:21",
"category": {
"id": 1,
"name": "Web development",
"created_at": "2018-09-05 20:54:54",
"updated_at": "2018-09-05 20:54:54"
}
}
]
/api/blog/ url via POST is redirected to the store function of my controller, to store the data in the database and the function looks like this:
public function store(Request $request)
{
$blog = new Blog($request->all());
$saved = $blog->save();
if ($saved) {
return response()->json($blog, 200);
}
return response()->json([
'message' => '400 Bad Request'
], 400);
}
then returns (missing category information)
{
"title": "title from blog",
"body": "texto do meu blog",
"category_id": "2",
"updated_at": "2018-09-06 00:56:13",
"created_at": "2018-09-06 00:56:13",
"id": 12
}
then, missing category information
after storing data in the database, I need it to come in response to category information for that specific blog
someone to help?
I can with
$blog->load('category');

hasManyThrough is returning only one result instead of all

The platform has a many to many relationship, where the table assigned_users holds all Users assigned to CalendarEvents.
I need to fetch, through CalendarEvents, the assigned users as an object, showing each user's information. So, I want to access the User through AssignedUsers, because I wanna fetch all the users related to that event.
public function assignedUsers()
{
return $this->hasManyThrough(Users::class, AssignedUsers::class, 'user_id', 'id');
}
It works, but it shows only the first user in the table. I want to show all of them. Currently there are 3.
"assigned_users": [
{ id: 1, ... }
]
If I do the following:
public function assignedUsers()
{
return $this->belongsToMany(Users::class, "assigned_users", "event_id", "event_id");
}
It will fetch 3 results, but all the information will be from the same user. It will repeat the same user 3 times. Changing both the event_id to user_id and id, and id and user_id will have the same result.
"assigned_users": [
{ id: 1, ... },
{ id: 1, ... },
{ id: 1, ... },
...
]
What I am trying to accomplish is the following result:
{
"status": "200",
"success": true,
"data": [
{
"event_id": 1,
"event_key": "EB1M7OGJRPW0",
"calendar_id": 4,
"start_at": "2018-01-01 00:00:00",
"end_at": "2018-01-31 00:00:00",
"location": "123 Lorem, Ipsum",
"event_name": "Event #1",
"description": "Lorem ipsum dolor sit amet",
"added_at": "2018-02-07 09:07:31",
"created_by": {
"id": 4,
"name": "Foo Bar",
"email": "foobar92#gmail.com",
"first_name": "Foo",
"last_name": "Bar",
"status": "active",
"is_activated": 0,
"created_at": "2018-02-07 09:06:49",
"updated_at": "2018-02-07 09:06:49"
},
"assigned_users": [
{
"id": 1,
...
},
{
"id": 2,
...
},
{
"id": 3,
...
},
...
]
}
]
}
This is not the case of hasManyThrough relationship. Here assigned_users is a pivot table so you just need a belongsToMany relationship defined
public function assignedUsers()
{
return $this->belongsToMany(Users::class, 'assigned_users', 'event_id', 'user_id');
}
and for this to work, you may have to change the calendar_events table's primary key to id (instead of event_id)

Transform Laravel Eloquent result

When I execute this in my Controller
$data = User::with('teams')->find(2);
return response(['data' => $data]);
I get this as result
{
"id": 2,
"country_id": 1,
"first_name": "John",
"last_name": "Doe",
"created_at": "-0001-11-30 00:00:00",
"updated_at": "2015-02-02 23:08:21",
"full_name": "John Doe",
"teams": [
{
"id": 1,
"name": "Helpdesk medewerker",
"description": ""
},
{
"id": 2,
"name": "Retentie",
"description": ""
}
]
}
Im not interested in the full teams data, but only interested in the teamNames of the user.
I've done this by adding this
$data->each(function($user) {
$user->team_names = $user->teams->lists('name');
unset($user->teams);
});
I was wondering if this is the correct way of modifying the Eloquent result.
You can use an attribute accessor for that. Add this to your model:
protected $hidden = ['teams']; // hide teams relation in JSON output
protected $appends = ['team_names']; // add custom attribute to JSON output
public function getTeamNamesAttribute(){
return $this->teams->lists('name');
}
To change hidden and appends dynamically you can use the setter methods:
$data = User::with('teams')->find(2);
$data->setHidden(['teams']);
Filter out the columns you want in your first build.
$data = User::with(['teams' => function($query){
return $query->select('name');
}])->find(2);
Will output:
{
...
"teams": [
{
"name": "Helpdesk medewerker"
},
{
"name": "Retentie"
}
]
}
You can also use the lists method.
$data = User::with(['teams' => function($query){
return $query->lists('name');
}])->find(2);
Will output:
{
...
"teams": ["Helpdesk medewerker", "Retentie"]
}

Resources