How to implement Self Referencing pivot table relationship with Eloquent - database

I have this scenario to be implemented
"Member - Album" many to many relationship is the comments. Member can post comments on any Album. Album can have comments from any number of Members. Some comments can be posted as a reply to other comments.
"reply_to" refers to the parent comment. That is, a comment can have several replies.
I'm using Eloquent and Lumen 5.5 in my project. I have gone through the documentation of both of these. I know how to implement relationship such as M:N, M:1 and etc.
I still couldn't find out how to implement this scenario using Custom Intermediate Table Models in Eloquent. Any help is much appreciated.
Note: I have removed other elements from the ERD to make it simple..

Try this
class Member
{
public function comments()
{
return $this->hasMany(Comment:class, 'Member_id'); // a member may has many comments
}
}
class Album
{
public function comments()
{
return $this->hasMany(Comment::class, 'Album_id'); // an album may has many comments
}
}
class Comment
{
public function parent()
{
return $this->belongsTo(Comment::class, 'reply_to'); // self reference
}
public function member()
{
return $this->belongsTo(Member::class, 'Member_id'); // a comment should belongs to a member
}
public function album()
{
return $this->belongsTo(Album::class, 'Album_id'); // a comment should belongs to an album
}
}
Querying:
Album::with(['comments' => function ($query) {
$query->with(['parent', 'member]);
}])->get();

As far as I could understand.
Below are the relationships required in the model.\
Comments model
Comment::belongsTo('Member'); //add namespace of member model
Comment::belongsTo('Albums'); //add namespace of member model
Member model
Member::hasMany('comments');
Album model
Album::hasMany('comments');
For not getting reply tocomments you can add where in Comment::belongsTo('Albums')->whereNull('replyTo');
Then can fetch reply comments via comment id.

Related

Laravel multiple user id tables

I'm pretty new to laravel and have a really basic question related to relationships.
Here is an example of my question:
I have a migration called money_transfers.
The migration contains the following things:
user_id (transfer sent by)
sentTo_id (transfer sent to)
amount
sent_at
BOTH user_id and sentTo_id refer to a User ID.
Now, what I want to do is the following:
Fetch the user the money was sent TO the same way as the user the money was sent BY. Just like in the example below:
$transfer->sentTo->name
or
$transfer->sentTo->id
You get what I mean. Thanks in advance :)
If you defined your foreign keys correctly in your migration table, Then it's just a matter of defining the right relationship:
class MoneyTransfer extends Model
{
public function sentBy()
{
return $this->belongsTo(User::class,'user_id');
}
public function sentTo()
{
return $this->belongsTo(User::class,'sentTo_id');
}
}
This way you can access the receiver attribute like this:
$transfer->sentTo->name;
And the sender attribute like this:
$transfer->sentBy->name;

Use two completely different classes as one

I have very stupid question about design patterns: let's say we have two classes Post and Product, for each of them we have different table in the DB, and they have nothing in common with each other, so we can't create base class for them. Some Posts even contains Products. And here's what we should do with them:
Somehow store Post and Product instances in the DB, pack them in one array(using C++, if it matters) when user requests news feed from the next item, send it to the client, and receive and unpack on the client side(using Java).
Next, we have to show both Post and Product in the one list(such as news feed on the Facebook).
Also, we can share Post or Product with our friends using chat. So we can send Post or Product as an attachment of the message(consequently, we should to store id of sent Post or Product in the column attached_item of the messages table in the DB on the server side).
So, what design pattern would be best here? How should I implement the Post and Product classes?
It is a very broad question, but here is a skeleton of what you could you, just to give you some ideas:
// An interface containing methods specific to objects you can list
interface Listable {}
// An interface containing methods specific to objects you can share
interface Shareable {}
// An interface containing methods specific to objects you can send
interface Sendable {}
class Post implements Listable, Shareable, Sendable {
List<Product> products;
}
class Product implements Listable, Shareable, Sendable {
}
class ListManager {
public void addToList(Listable element) { }
}
class ShareManager {
public void share(Shareable element) { }
}
class SendManager {
public void send(Sendable element) { }
}
You could then use Post and Product interchangeably this way:
Post post = new Post();
Product product = new Product();
ListManager listManager = new ListManager();
listManager.addToList(post);
listManager.addToList(product);
ShareManager shareManager = new ShareManager();
shareManager.share(post);
shareManager.share(product);
SendManager sendManager = new SendManager();
sendManager.send(post);
sendManager.send(product);
Regarding the database representation, as suggested fusiled in his comment, just stick them in 2 separate tables. With a mapping table in between to link the products to their post.
EDIT
Regarding the issue with the MESSAGES table
You could add a new mapping table MESSAGE_ATTACHED_ITEM with columns messageId, postId, productId. Only set a value to the relevant colum when attaching an item to a message
Or an other option would be to have an ATTACHED_ITEM table with an id only.
And have Post and Product tables to have a foreign key to this table Id.
you can then stick this attachedItemId into your attached_item column
I think the solution could be simpler than you think. Why don't you ust use a common Java-like interface and hide the implementation details?
Just implement a common interface with the methods you need. Supposing this common interface is called EntityInterface:
public class Post implements EntityInterface {};
public class Product implements EntityInterface {};
Then when you want to handle these classes, you treat them as an EntityInterface object:
EntityInterface myNewPost = new Post();
EntityInterface myNewProduct = new Product();
//Now you see myNewProduct and myNewPost as EntityInterface objects
These code fragments are in Java, but use virtual functions in C++ and you get the same.

Eloquent relation with where condition on model attribute

I have two models Student and StudentRevision with Student model having hasMany relation with StudentRevision model. I have defined a hasMany relation in Student as
public function revisions()
{
return $this->hasMany(
'StudentRevision',
'sid'
);
}
I have a field in students table (Student model) which references current revision of student from student_revisions table.
The table structure is something like this.
students sid srid name ....
student_revisions srid sid batch ....
Now i want to define hasOne relation with StudentRevision model which references current revision linked with Student. Currently I have defined this relation as:
public function current()
{
return $this->hasOne(
'StudentRevision',
'sid'
)
->where('srid', $this->srid);
}
But the problem with this relation is, that $this->srid is not available during query building process and can be only there after the actual model is available.
Please help how to overcome this.
I don't think you can define it as relation. But what you can do is this:
public function current(){
return $this->revisions()->where('srid', $this->srid)->get();
}
This way you can access it by $student->current(). You can even go a bit further and make it more relationship like:
public function current(){
return $this->revisions()->where('srid', $this->srid);
}
public function getCurrent(){
return $this->current()->get();
}
protected $appends = array('current');
Here we define an accessor for our attribute. Laravel Docs (scroll down to the bottom)
We can then use it like this:
$student->current; // retrieves the model
$student->current(); // retrieves an instance of the query builder

Traversing relationships (simple)

Bear with me please, I'm still learning.
I have 4 Models as so:
class Model_Users extends Model_Table {
public $table="users";
function init(){
parent::init();
$this->addField('name')->mandatory('Enter Name');
$this->addField('email')->mandatory('Enter E-Mail');
$this->addField('phone')->mandatory('Enter Phone');
$this->addField('password')->type('password')->mandatory('Enter Password');
$this->addField('is_superadmin')->type('boolean');
$this->addField('is_employee')->type('boolean');
$this->addField('is_manager')->type('boolean');
$this->hasMany('companies');
}
}
class Model_areas extends Model_Table {
public $entity_code='areas';
function init(){
parent::init();
$this->addField('name');
$this->addField('description')->type('text');
//$this->addField('companies_id')->refModel('Model_companies');
$this->hasOne('companies','companies_id','name')->mandatory(true);
$this->hasMany('sites');
}
}
class Model_areas extends Model_Table {
public $entity_code='areas';
function init(){
parent::init();
$this->addField('name');
$this->addField('description')->type('text');
//$this->addField('companies_id')->refModel('Model_companies');
$this->hasOne('companies','companies_id','name')->mandatory(true);
$this->hasMany('sites');
}
}
class Model_sites extends Model_Table {
public $entity_code='sites';
function init(){
parent::init();
$this->addField('name');
$this->addField('description')->type('text');
$this->addField('qrcode');
//$this->addField('Company Name','areas_id')->refModel('Model_companies','name');
$this->hasOne('areas','areas_id','name');
}
}
I have the "sites" model in a simple crud. It is successfully pulling the relevant hasOne record from "areas". I have two questions:
1) How do I change the column names for the joined areas column? It just says "Areas", whereas I want it to be "Area Name"
2) And the more complex one: How can I perform something like a grid->addColumn to the resulting CRUD (or would it have to be a grid?) that would pull the company name linked to the area in areas_id? Its all 1 to many relationships down the line. Company has multiple areas. Areas has multiple sites. I want to add the company name to the CRUD view of Sites.
You can see in the commented lines some of my minor attempts at accomplishing this. Then I realized I'm missing something big. I should be able to keep this model simple and simply traverse the relationships..
Thank you for your help.
Back to these tutorial videos.
Edit: OK the column name I figured out. ->caption('Blah'). Still can't figure out the traversal :(
1) Try:
$this->hasOne('areas','areas_id','name')->caption('Area Name');
2) Slightly simplified your models and here it is. I admit - I didn't test this, but it should work:
<?php
class Model_Company extends Model_Table {
public $table = 'company';
function init(){
parent::init();
$this->addField('name');
$this->addField('description');
$this->hasMany('Area');
}
}
class Model_Area extends Model_Table {
public $table = 'area';
function init(){
parent::init();
$this->addField('name');
$this->addField('description');
$this->hasOne('Company', 'company_id', 'name');
$this->hasMany('Site');
}
}
class Model_Site extends Model_Table {
public $table = 'site';
function init(){
parent::init();
$this->addField('name');
$this->addField('description');
$this->hasOne('Area', 'area_id', 'name');
// join area and company tables
$j_area = $this->leftJoin('area', 'area_id');
$j_company = $j_area->leftJoin('company', 'company_id');
// add fields from joined tables to this model
$j_area->addField('area_name', 'name');
$j_company->addField('company_name', 'name');
}
}
Basic idea - use joins. ->leftJoin will not create joined records, but ->join will create them. Just for reporting (grid, crud etc.) you're fine with leftJoin.
P.S.
Define some kind of coding rules for yourself. For example, when you use uppercase, lowercase, camel-case etc for class names, filenames. Otherwise you'll sooner or later run into problems when moving on *NIX systems.
Good example is to name all classnames with first letter in upper case. Don't forget that your respective file names should exactly match your classname - also letter case.
Page classnames I like to name all in lowercase, because their name is used in URLs and then it looks better lowercased. But that's just me :)
One more. If you're working on new project or are just learning ATK, then stick to newest development version (available on github). There are quite many things which have changed since recording of tutorials. For example $entity_code is deprecated - use $table instead. refModel is obsolete too I guess etc.
If you want to be successful with ATK, then you have to look regularly into ATK source code to understand it better. Also there are some useful comments :)
Good luck!

Database One-to-Many with two foreign key fields in Laravel

I have been trying to define some Database schema to use the laravel framework. I want to model a Football match. The first step I wanted to do is to define the Entity Relationship diagram, but I found this (which I thought would be quite trivial) to be confusing in some aspects.
First, the obvious approach is to say that a Match is related with two Teams, and a Team is related to any number of Matches. So, we would have a "Many to Many" relationship.
But the implementation of a many to many relation is to have two tables and an intermediate table to relate both entities. I think this would be too much, when I know that a Match will always have two Teams and simply having two columns (local_id and visitant_id) with foreign keys to the Teams table would be enough. Plus, I want to be able to do:
Match::find(1)->local() or Match::find(1)->visitant();
So, thinking on this I am implementing a "One to Many" relation, but with this I have another issue. To retrieve all the matches a Team has played I would like to do:
Team::find(1)->matches();
But I cannot do this because I can only specify one key column when defining the matches() method in eloquent (by default it would be team_id, but it should be visitant_id and local_id).
After some more digging into the source code I found there is a way to actually keep my database schema as it is and achieve what I want (at least in Laravel 4). I posted my problem in github and Taylor Otwell (creator of the framework) gave me the correct answer: https://github.com/laravel/framework/issues/1272
Quoting him, it should be as easy as this:
class Team extends Eloquent {
public function allMatches()
{
return $this->hasMany('Match', 'visitant_id')->orWhere('local_id', $this->id);
}
}
And then...
$team = Team::find(2);
$matches = $team->allMatches;
This is one of those famous database design problems. Friendship relationships, for instance, suffer from that same difficulty. Since you are using Eloquent, I would suggest you to stick with many to many approach and have an extra boolean column local on your intermediate table
class Match extends Eloquent {
public $includes = array('team'); // Always eager load teams
public function teams() {
return $this->has_many_and_belongs_to('team')->with('local');
}
public function get_local() {
foreach ($this->teams as $team) {
if ($team->pivot->local) return $team;
}
}
public function get_visitant() {
foreach ($this->teams as $team) {
if (!$team->pivot->local) return $team;
}
}
}
class Team extends Eloquent {
public function matches() {
return $this->has_many_and_belongs_to('match')->with('local');
}
// I'm doing separate queries here because a team may have
// hundreds of matches and it's not worth looping through
// all of them to retrieve the local ones
public function matches_as_local() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 1);
}
public function matches_as_visitant() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 0);
}
}
Obs:
The method has_many_and_belongs_to(...)->with('field') has nothing to do with eager loading. It tells Eloquent to load the intermediate table column field and put that in the pivot.
Usage:
$match = Match::find(1);
$match->local; // returns local team
$match->visitant; // returns visitant team
$team = Team::find(1);
$team->matches; // returns all matches
$team->matches_as_local; // ...
$team->matches_as_visitant; // ...
foreach ($team->matches as $match) {
if ($match->pivot->local) {
// put nice local icon here
} else {
// put nice visitant icon here
}
}

Resources