How do I get rows based on another table with Eloquent? - php

I have one table with profiles. In this table there is no way to know what user have access to each row.
CREATE TABLE `rw_profiles` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
)
Then I have another table to link each user with a profile. This table can contain same profile_id but different user_id (many users can access same profile).
CREATE TABLE `rw_profile_access` (
`profile_id` int(10) unsigned NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
UNIQUE KEY `profile_access_profile_id_owner_unique` (`profile_id`,`owner`),
KEY `profile_access_user_id_foreign` (`user_id`),
CONSTRAINT `profile_access_profile_id_foreign` FOREIGN KEY (`profile_id`) REFERENCES `rw_profiles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `profile_access_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `rw_users` (`id`)
)
How do I list all profiles that a user has access too? Are you supposed to use the with-method?
Something like $profiles = Profile::with('...')->get();?
Update
As of now I'm joining the table.
$profiles = Profile::join('profile_access', function($query) use ($user) {
$query
->on('profile_id', '=', 'profiles.id')
->on('user_id', '=', DB::raw($user['id']));
})->get(['profiles.*']);
That works. Is this the only way or can I do it without join?

You need to define the many-to-many relationship in your User and Profile models. They need methods like:
class User {
public function Profiles()
{
return $this->belongsToMany('Profile', 'rw_profile_access', 'user_id', 'profile_id');
}
}
class Profile {
public function Users()
{
return $this->belongsToMany('User', 'rw_profile_access', 'profile_id', 'user_id');
}
}
The parameters for Profiles() and Users() correspond to:
Related model
Pivot table
Foreign key
Foreign key
You can then query like this:
$users_with_their_profiles = User::with('profiles')->get();
$users_that_definitely_have_profiles = User::with('profiles')->has('profiles')->get();
The Laravel documentation explains this thoroughly at http://laravel.com/docs/eloquent#relationships.

You can use many to many relationship.
Laravel table relationship
http://laravel.com/docs/eloquent#working-with-pivot-tables
OR:
Assume you have ProfileAccess model.
$profiles = ProfileAccess::with('profile')
->where('user_id', $user['id'] )
->get();
foreach($profiles as $profile) {
dd($profile->profile->name);
}

Related

How to access to sub related model when the related model returns a collection

I have 3 models: User, Payment and Log. A User has many Payment and both User and Payment have many Log.
User Model
class User
{
public function payments()
{
return $this->hasMany('Payment', 'user_id');
}
public function logs()
{
return $this->morphMany(Log::class, 'loggable');
}
}
users table
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Payment Model
class Payment
{
public function user()
{
return $this->belongsTo('User', 'user_id');
}
public function logs()
{
return $this->morphMany(Log::class, 'loggable');
}
}
payments table
CREATE TABLE `payments` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`status` varchar(50),
`amount` int(11) NOT NULL,
`collection_date` date NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`user_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_payments_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Log Model
class Log
{
public function loggable()
{
return $this->morphTo();
}
}
logs table
CREATE TABLE `logs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`loggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`loggable_id` bigint(20) unsigned NOT NULL,
`old_values` text COLLATE utf8mb4_unicode_ci,
`new_values` text COLLATE utf8mb4_unicode_ci,
`user_id` bigint(20) unsigned DEFAULT NULL, /* the user that made the change, if any */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
The Log model stores all changes made to any other model (it's a polymorphic relationship), so if the user changes its name, the Log model will store the older name and the new name. The same applies to Payment: if a payment status changes the Log model will have a new record with the old status and the new status.
I need to show a paginated list of all Log records for a specific User ordered by date. So my code is:
$user = App\User::find($id);
$allLogs = $user->logs();
// Now I need to join (I'm using union) both sets of logs
$allLogs->union($user->payments->logs());
However, since a User can have many Payment, $user->payments returns a Collection, so is no longer a query builder/eloquent object and it fails when I try to call ->logs().
$user->payments()->logs() also doesn't work, because $user->payments() returns a HasMany object and the ->logs() method doesn't exist.
I'm trying to avoid getting each collection of Log separately and then processing them using php (it would be perfect to delegate that task to MySql).
I believe it can be done, because I can write the query on MySql:
select l.*
from payments p
join logs l on p.id = l.loggable_id and l.loggable_type = 'App\\Payments'
where p.user_id = SOMEUSERID
Thanks in advance
Eager load the relations(reduces number of queries)
$user = User::with(['payments.logs', 'logs'])->find($id);
Query using the Log model.
$logs = Log::where([
'loggable_id' => $user->id,
'loggable_type' => 'User',
])
->orWhere(function($query){
$query->whereIn('loggable_id',
$user->payments()->pluck('id'))
->where('loggable_type', 'Payment');
})->get();
OR
Get them individually and then combine them.
$all_logs = collect([]);
$all_logs->push($user->logs);
foreach($user->payments as $p){
$all_logs->push($p->logs);
}
$final_logs = $all_logs->collapse();
OR
Just use the relations, without iterating over the payments. You can combine the results if you want(as shown in the previous approach).
$user_logs = $user->logs;
$payment_logs = $user->payments->pluck('logs')->collapse();

How do you query a child and parent in laravel eloquent

I want to figure out how to query to get a result that was either created by the current user or the current users parent.
I have two tables users and workouts. Both tables have a created_by column, which stores the user id of whoever created said user or workout record.
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`created_by` int(10) unsigned DEFAULT NULL,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `users_created_by_foreign` (`created_by`),
CONSTRAINT `users_created_by_foreign` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`)
);
CREATE TABLE `workouts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`created_by` int(10) unsigned NOT NULL,
`description` text COLLATE utf8_unicode_ci,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `workouts_created_by_foreign` (`created_by`),
CONSTRAINT `workouts_created_by_foreign` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`)
);
In my user model I have a function to return all the workouts that the user has created.
public function createdWorkouts(): HasMany
{
return $this->hasMany(Workout::class, 'created_by');
}
What I want to figure out is, how can I query to find a workout using a ID but only for workouts the current user has created or the creator of the current user has created. Below is my current route logic, which only returns a workout created by the current user.
$user = User::find(1); // This would come from the JWT
if (!$workout = $user->createdWorkouts()->find($args[‘workout_id’])) {
return $response->withJson([], 404);
}
For this instance, I probably wouldn't use an eloquent relationship as it could be either the users id or their created_by value.
Instead, I would change the method on your user model to read:
public function createdWorkouts()
{
return Workout::where('created_by', $this->id)->orWhere('created_by', $this->created_by)->get();
}
Then you can do $user->createdWorkouts() which will return you a collection of all workouts where it was created by the user, or created by the user that created the user.
As it is a collection, you then have access to all of the other methods on a collection such as find etc

Laravel: Use model to return database row based on linked table.

I need to use my Website model to get a row from my database within the websites table, however this row is identified through my domains table.
So basically it would be great to do a query on my domains table and match the row, then from that get the website row from the websites table using the website_id column.
But I want to simply pass this data into my controller by just referencing the Model within the method.
class WebsiteController extends Controller {
public function index(Website $website) {
print_r($website);
return view('index');
}
}
My domains table:
CREATE TABLE `domains` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`website_id` INT(11) NOT NULL DEFAULT '0',
`domain` VARCHAR(255) NOT NULL DEFAULT '0',
`active` INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
INDEX `website_id` (`website_id`),
CONSTRAINT `website_id` FOREIGN KEY (`website_id`) REFERENCES `websites` (`id`)
)
COMMENT='This table will contain all of the domains registered on MarvWeb, this will link to the website record. '
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=3;
And websites table:
CREATE TABLE `websites` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NULL DEFAULT NULL,
`tagline` VARCHAR(255) NULL DEFAULT NULL,
`description` VARCHAR(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COMMENT='This table will contain all the websites data. '
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2;
Does this make sense?
Add a website function to your Domain model.
class Domain extends Model{
public function website(){
return $this->hasOne('App\Website');
}
// remainder of model.
}
When you retrieve the Domain query results, the website can be accessed by
print_r($domainRowResult->$website->tagline);

Eloquent Join With Pivot Table?

I have a pivot table called message_recipients that looks like
CREATE TABLE `message_recipients` (
`user_id` bigint(20) unsigned NOT NULL,
`message_id` bigint(20) unsigned NOT NULL,
KEY `message_recipients_user_id_index` (`user_id`),
KEY `message_recipients_message_id_index` (`message_id`),
CONSTRAINT `message_recipients_message_id_foreign` FOREIGN KEY (`message_id`) REFERENCES `messages` (`id`),
CONSTRAINT `message_recipients_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
And then I have a messages table
CREATE TABLE `messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sender_id` bigint(20) unsigned NOT NULL,
`subject` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`message` longtext COLLATE utf8_unicode_ci NOT NULL,
`status` tinyint(4) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `messages_sender_id_index` (`sender_id`),
CONSTRAINT `messages_sender_id_foreign` FOREIGN KEY (`sender_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=110 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
And the default user table.
And I have the following classes
class MessageRecipients extends Eloquent {
protected $table = 'message_recipients';
public $timestamps = false;
public function message() {
return $this->hasMany('App\Models\Message');
}
public function user() {
return $this->belongsTo('App\Models\User');
}
public function me() {
return $this->belongsTo('App\Models\User');
}
}
class Message extends Eloquent {
public function recipients() {
return $this->hasMany('App\Models\MessageRecipients');
}
public function sender() {
return $this->belongsTo('App\Models\User');
}
}
And I am trying to figure out how to generate the query. I have
$message = Message::with('App\Models\MessageRecipients')->where('message_recipients.user_id', '=', 1)->first();
But I keep getting an error about missing message_recipients column. How can I generate a query that would look like (or produce the same data as)
SELECT
*
FROM
messages
JOIN
message_recipients
ON
messages.id = message_recipients.message_id
WHERE
message_recipients.user_id = 1
Or basically select all the messages with message_recipients data that are assigned to that user.

How to get array with rows related to another table for Bllim Datatable - Laravel

I use Bllim/Datatables package for the datatables on my web application; but I can't retrieve all the related rows.
I use the code below:
$books= $client->books()->where('books.type', 0);
And I send it to Datatables::of method as follow:
return Datatables::of($books)
->edit_column('type', '{{$type}}')
->edit_column('created_at', function($obj) {
return $obj->created_at->format('d/m/Y (h:i)');
})
->set_index_column('id')
->make();
But all this return an Internal Server Error (500) with the follow message:
{"error":
{
"type":"ErrorException",
"message":"Undefined property: Illuminate\\Database\\Eloquent\\Builder::$columns",
"file":"\/var\/www\/clients\/myapp\/vendor\/bllim\/datatables\/src\/Bllim\/Datatables\/Datatables.php",
"line":256
}
}
MY DATABASE STRUCTURE:
create table clients(
id int not null auto_increment,
name varchar(10) not null,
password varchar(60) not null,
unique(name),
primary key(id),
created_at timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
updated_at timestamp NOT NULL DEFAULT now() ON UPDATE now()
)CHARSET utf8 COLLATE utf8_spanish_ci;
/* PIVOT TABLE */
create table book_client(
id int not null auto_increment,
book_id int,
client_id int,
primary key(id),
FOREIGN KEY (book_id ) REFERENCES books(id) ON DELETE CASCADE,
FOREIGN KEY (client_id ) REFERENCES clients(id) ON DELETE CASCADE
)CHARSET utf8 COLLATE utf8_spanish_ci;
create table books(
id int not null auto_increment,
name varchar(50) not null,
description varchar(500),
type varchar(10) not null,
primary key(id),
created_at timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
updated_at timestamp NOT NULL DEFAULT now() ON UPDATE now()
)CHARSET utf8 COLLATE utf8_spanish_ci;
In the views I have the next:
/*In the view of clients*/
public function books(){
return $this->belongsToMany("Book");
}
/*In the view of books: (yes, in my case, a book could belong to multiple clients*/
public function clients(){
return $this->belongsToMany("Client");
}
Anybody Know the method for make I need?
You need to use the next sentence:
/* The client whose ID is 1*/
$client = Client::find(1);
$client->books()
->getQuery()
->getQuery()
->select(array('id', 'ColumnA', 'ColumnB'));
You can use the where clausule also:
$client->books()
->getQuery()
->getQuery()
->select(array('id', 'ColumnA', 'ColumnB'))
->where('id','=',1);
Note which I used getQuery() twice, this is because Bllim/Datatables need an Query/Builder object.

Categories