How to get a column relationship based on field - sql

I have a chat table that both a user and admin can chat the table is defined as follow:
id, from_id, to_id, message, is_from_admin.
what I want is, if the is_from_admin is true laravel should use the admin table at sql level for the from. otherwise it should use the user table for from and same applies to the to field. Thanks

If you have the chance, I'd rework the table a bit and name it like so:
id, from_user_type, from_user_id, to_user_id, message
The pair from_user_type and from_user_id can be used to creat a custom polymorphic relation ("type" refers to the model/table name, and "id" refers to the id of a row in this table) as seen here: https://laravel.com/docs/9.x/eloquent-relationships#one-to-many-polymorphic-relations .
If you also want to send admin-to-admin, you should also add to_user_type, to_user_id so you can create a polymorphic relationship on the receiving side as well.
The polymorphic relation will look something like this:
class ChatMessage
{
public function fromUser()
{
// This function should automatically infer `from_user_type` and `from_user_id`
// from this function name.
return $this->morphTo();
}
}
class AdminUser
{
public function chatMessages()
{
return $this->morphMany(ChatMessage::class, 'fromUser');
}
}

Laravel can not solve what you are doing, which is a polymorphic relationship, based on a boolean. Theoretically you could bind the polymorphic class definition to 0 or 1, but this is a hack at best. Alternatively you could rewrite your table structure to support polymorphic relations.
Instead i would say you achieve something that is working, with what you have. Create two relationships combined with some logic in an accessor. Create a relationship for the admin and for the user.
Chat extends Model
{
public function fromAdmin()
{
return $this->belongsTo(Admin::class, 'from_id')->where('is_from_admin', true);
}
public function fromUser()
{
return $this->belongsTo(User::class, 'from_id')->where('is_from_admin', false);
}
}
Now create the accessor on the Chat model, using your new relationships.
public function getFromAttribute()
{
return $this->fromAdmin ?? $this->fromUser;
}
With this approach, you should be able to access the attribute like this.
Chat::find(1)->from; // either a user or admin based on the data.

Related

Gii doesn't recognize many-to-many relationship to itself?

So I am trying to implement a friendlist, the above is the SQL diagram I made for my simple project and after generating the Models. I realized there was something wrong with the way Gii generated the model.
I wanted to make a many-to-many relationship with User to itself, but this is what I got:
class User {
...
public function getPosts()
{
return $this->hasMany(Post::className(), ['userId' => 'id']);
}
}
class Friend {
...
public function getFriend()
{
return $this->hasOne(Member::className(), ['id' => 'friendId']);
}
}
The User class doesn't have any relationship with itself, I expected something like getUsers() inside of User, but it didn't generate it. I initially thought about not making a model with the junction table, but I did so just to see what would happen. I don't think I need it. So I am not sure how to do this correctly? Do I need to get rid of my Junction Table Models and Do I need to make the relationship between User to itself and User to Message manually? I thought about doing a many-to-many in User and Message and a many-to-many in User for User. Is this the right thing? Tell me if I am wrong. Thank you.
You are on a true way. You need a junction table for implementing your goal. Easily as you done this, you must define two model: User and Friend. Now on your User model at first you must define a relation for get the list of all friends, Suppose call it getFriendsLists:
public function getFriendsLists()
{
return $this->hasMany(Friend::className(), ['userId' => 'id']);
}
This relation says that "Get me all account that are connected with me, i.e. if my id is 102, this relation return all record of friend table that their userIds are 102". Well, now we get all friends with a relation on User model, let call him getFriends:
public function getFriends()
{
return $this->hasMany(User::className(), ['friendId' => 'id']
->via('friendsList');
}
Notice that 'friendsList' as is a parameter of via method, is our predefined relation on top of this answer. Now easily you can get all account that are friends of our example (User with id 102):
public FriendController extends Controller
{
// Some code goes here!
public function actionFriendList($id)
{
$user = User::findOne($id);
$friends = $user->friends;
return $this->render('friend-list', ['friendsArray' => $friends]);
}
}
And use them on your friend-list view file as $friendsArray variable. Extra note that $user->friends use friends relation that you defined on User model with getFriends method.

How to define a hasMany far relationship

Suppose you have
Facility 1------* Section 1------* Session *------1 Event
I.e., a facility has many sections; each section can hold many sessions; each session belongs to an event.
How can I define this as a relationship in the Facility model to retrieve all unique instances of Event that a facility is hosting? I have tried this:
class Facility extends Eloquent\Model {
public function events() {
return $this->hasMany('Event')
->join('session', 'session.event_id', '=', 'event.id')
->join('section', 'section.id', '=', 'session.section_id')
->join('facility', 'facility.id', '=', 'section.facility_id');
}
}
I don't know if I'm very close with that; Laravel adds a constraint implicitly ("events"."facility_id" in (...)) and everything gets messed up.
What is the proper way of doing this?
I would avoid putting any logic in a relationship method on your models. Instead use eloquent to load your relationships, either one after the other or eager load into the Facility model. The latter being preferable.
class Facility extends Eloquent\Model
{
public function sections()
{
return $this->hasMany('Section');
}
}
Just make sure each of the models has the correct relationship set up so you can chain an eager load. Such as:
class Section extends Eloquent\Model
{
public function sessions()
{
return $this->hasMany('Session');
}
}
Then when you come to load the relationships you can use dot notation to eager load them.
$facility = Facility::with('sections.sessions.event')->get();
$facility will now contain a Facility model with nested relationships. You can use some of the different array helpers to extract/pluck all of the events.
This is the closer i've got to my initial purpose:
I created an SQL view:
CREATE VIEW facility_events AS
SELECT DISTINCT ON (e.id) e.id,
e.name,
e.created_at,
e.updated_at,
f.id AS facility_id
FROM events e
JOIN sessions s ON e.id = s.event_id
JOIN sections_extended fse ON s.section_id = fse.id
JOIN facilities f ON fse.root_facility_id = f.id;
Then I create the corresponding FactoryEvent Eloquent model and, finally, in my class Facility:
public function events() {
return $this->hasMany('App\FacilityEvent');
}
I look forward to see Laravel-only solutions for this. In other frameworks, such as Yii, I have been able to do things like that without the need to work on the database directly.

Which relationship type to choose in Laravel Eloquent ORM on multiple relationships case

I am new to Laravel and a bit confused about some definitions of ORM.
I am currently working on a simple Trouble ticket management system, and here is my question :
(table: column, column,...)
tickets : id, description, equipment_id
equipments: id, name, vendor_id
vendor: id, name
This is a very short resume of my tables and its relations, following Laravel's conventions. How can I build these models?
Basically I need to retrieve, for example, how many tickets were opened to a certain vendor (how many times I called the vendor for support).
Thank you in advance
What zwacky said is entirely (edit: maybe not entirely correct in the end) true for close relations, but in your situation there is nested relation:
Vendor -> Equipment -> Ticket
Then to retrieve tickets for particular vendor you would define relation on Vendor model like this:
class Vendor extends Eloquent {
public function equipment()
{
return $this->hasMany('Equipment');
}
public function tickets()
{
return $this->hasManyThrough('Ticket', 'Equipment');
}
class Equipment extends Eloquent {
public function tickets()
{
return $this->hasMany('Ticket');
}
public function vendor()
{
return $this->belongsTo('Vendor');
}
class Ticket extends Eloquent {
public function equipment()
{
return $this->belongsTo('Equipment');
}
and to get count of total tickets for the vendor (not currently open):
Vendor::find($id) // retrieve particular vendor
->tickets()->count(); // get count on tickets table
// and this way you retrieve collection of related tickets
Vendor::find($id) // retrieve particular vendor
->tickets; // get Eloquent Collection
Also you may find it helpful: http://softonsofa.com/querying-relations-with-eloquent-in-laravel-4/
you'd need to declare these relationships within their models. e.g. your Ticket.php model could look like this:
class Ticket extends Eloquent {
public function equipment()
{
return $this->hasOne('Equipment');
}
public function vendor()
{
return $this->hasOne('Vendor');
}
...
}
for retrieval you'd do it like this:
foreach (Ticket::all() as $ticket) {
$ticket->vendor()->id;
}
check this section of the laravel docs.
edit: for the specific query how many tickets are open to a certain vendor:
Ticket::where('open', '=', 1)->vendor()->where('id', '=', 42);

Implementing GUID in Yii

If you have a data model in which one table is a guid table with just a guid column, and many tables have a primary key referencing that guid, how would you recommend incorporating this type of logic into Yii? To create a new model of any guidable thing, you have to create a guid first. Where is the right place to put this sort of logic?
Edit: To be more specific, here is the issue I am facing:
I have a table of guids, tbl_guid, with one column guid that is a MySQL BIGINT
Some tables, like tbl_foo, have a PK guid referencing guid in tbl_guid
The Foo model has the relation self::BELONGS_TO, 'Guid', 'guid'
I do not want to create a new guid until I am definitely ready to create Foo
I'd like to somehow delay the saving of my guid until I'm actually saving (and have otherwise validated) Foo
However, Foo never validates, because it doesn't have a guid.
Edit 2: I've posted my own answer, but I am hoping somebody has a better answer/improvement to suggest. Here are the issues with my answer:
How can we force the owner to comply with some interface, so we don't have to throw in a bunch of conditionals to check for whether or not the owner has an attribute or method, etc
Even though the database will not accept null for guid, it still seems wrong to remove guid from the list of required attributes.
This is the best I've been able to come up with:
Create a new CActiveRecordBehavior for guidable models:
public function beforeSave() {
if (!$this->owner->guid) {
$guid = new Guid;
$guid->save();
$this->owner->guid = $guid->guid;
}
}
Attach the behavior on the model, or define it in the model's behaviors array.
public function init() {
$behavior = new GuidBehavior;
$this->attachBehavior('GuidBehavior', $behavior);
}
Remove the required-ness of guid so validation doesn't fail:
array('name', 'required'), //guid isn't here
Test
$brand->save();
Inheritance. If you implemented my BaseModel code a while ago, you can override the __construct() method in the BaseModel to create an instance of the GUID class.
BaseModel:
public function __construct()
{
parent::__construct();
$newGuid = new GUID();
return $this;
}
See http://www.yiiframework.com/doc/api/1.1/CActiveRecord

Returning datasets from LINQ to SQL in a REST/WCF service

I have a WCF/REST web service that I'm considering using Linq to SQL to return database info from.
It's easy enough to do basic queries against tables and return rows, for example:
[WebGet(UriTemplate = "")]
public List<User> GetUsers()
{
List<User> ret = new List<User>(); ;
using (MyDataContext context = new MyDataContext())
{
var userResults = from u in context.Users select u;
ret = userResults.ToList<User>();
}
return ret;
}
But what if I want to return data from multiple tables or that doesn't exactly match the schema of the table? I can't figure out how to return the results from this query, for example:
var userResults = from u in context.Users
select new { u.userID, u.userName, u.userType,
u.Person.personFirstname, u.Person.personLastname };
Obviously the resulting rowset doesn't adhere to the "User" schema, so I can't just convert to a list of User objects.
I tried making a new entity in my object model that related to the result set, but it doesn't want to do the conversion.
What am I missing?
Edit: related question: what about results returned from stored procedures? Same issue, what's the best way to package them up for returning via the service?
Generally speaking, you shouldn't return domain objects from a service because if you do you'll run into issues like those you're finding. Domain objects are intended to describe a particular entity in the problem domain, and will often not fit nicely with providing a particular set of data to return from a service call.
You're best off decoupling your domain entities from the service by creating data transfer objects to represent them which contain only the information you need to transfer. The DTOs would have constructors which take domain object(s) and copy whatever property values are needed (you'll also need a parameterless constructor so they can be serialized), or you can use an object-object mapper like AutoMapper. They'll also have service-specific features like IExtensibleDataObject and DataMemberAttributes which aren't appropriate for domain objects. This frees your domain objects to vary independently of objects you send from the service.
You can create a Complex Type and instead of returning Anonymous object you return the Complex Type. When you map stored procedures using function import, you have a option to automatically create a complex type.
Create a custom class with the properties that you need:
public class MyTimesheet
{
public int Id { get; set; }
public string Data { get; set; }
}
Then create it from your Linq query:
using (linkDataContext link = new linkDataContext())
{
var data = (from t in link.TimesheetDetails
select new MyTimesheet
{
Id = t.Id,
Data = t.EmployeeId.ToString()
}).ToList();
}