I'm trying to figure out how to make the most efficient messaging system for users.
My idea is that (very similar to Facebook) when a user sends a message to another user, then it creates a thread where all messages send between the users are displayed. It is important that new messages are flagged as unread for the recipient.
I want to leverage Laravels relations.
If I create a pivot table that contains the recipient id and sender id, how can I get Laravel to differentiate the 2 users using Laravels relations, so that I can easily get a list of threads where the user is involved, whether he's just the recipient, sender or both.
A thread does not need a title/subject.
I would do something like this:
class User extends Model {
public function threads() { return $this->belongsToMany(Thread::class)->withPivot('last_read_message_id'); }
}
class Thread extends Model {
public function users() { return $this->belongsToMany(User::class)->withPivot('last_read_message_id'); }
public function messages() { return $this->hasMany(ThreadMessage::class); }
}
class ThreadMessage extends Model {
public function thread() { return $this->belongsTo(Thread::class); }
public function author() { return $this->belongsTo(User::class); }
}
// List of threads where a user is involved:
Auth::user()->threads;
With this design, you can also handle groups of discussion, which is quite nice.
Related
I am working on a project in Laravel where I have two table.
- "teams" (table) with "Team" (model)
- "players" (table) with "Player" (model)
Users can pick any combination of 12 players to make their own team so I am trying to create "one to many" relationship where players will have only one record for each player but many teams can have same player in them.
"players" table have column "id" as primary key.
"teams" table have 12 columns like "player1_id", "player2_id", "player3_id" and so on and every column will act as a foreign key of the "id" in players table.
My clear cut question is - what code do I need to put in both models (Player and Team) so I can access the information through Eloquent.
so far I have put this code in my teams table but not sure if this is the right thing to do.
for the players table, I have no clue what to write in there.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Team extends Model
{
//
public function player1()
{
return $this->belongsTo('App\Player', 'player1_id');
}
public function player2()
{
return $this->belongsTo('App\Player', 'player2_id');
}
public function player3()
{
return $this->belongsTo('App\Player', 'player3_id');
}
public function player4()
{
return $this->belongsTo('App\Player', 'player4_id');
}
public function player5()
{
return $this->belongsTo('App\Player', 'player5_id');
}
public function player6()
{
return $this->belongsTo('App\Player', 'player6_id');
}
public function player7()
{
return $this->belongsTo('App\Player', 'player7_id');
}
public function player8()
{
return $this->belongsTo('App\Player', 'player8_id');
}
public function player9()
{
return $this->belongsTo('App\Player', 'player9_id');
}
public function player10()
{
return $this->belongsTo('App\Player', 'player10_id');
}
public function player11()
{
return $this->belongsTo('App\Player', 'player11_id');
}
public function bonus_player()
{
return $this->belongsTo('App\Player', 'bonus_player_id');
}
}
There are several ways to do it:
1- You can put the team players in a separate table, let's say team_players, with columns, team_id, player_id, and position. validate in your code that always 12 players are present. This would be a many-to-many relationship.
in this scenario, you can use this code in your Team model:
public function players() {
return $this->belongsToMany(Player::class, 'team_players');
}
Handling the logic in code can be a bit troubling for a laravel beginner though.
2- You can simply perform a query based on the order:
public function player(int $position){
$playerId = $this->attributes('player'.$position.'_id');
return Player::find($player);
}
3- you can define magic methods, although I strongly discourage you from doing so.
public function __call(string $name , array $arguments){
if (Str::beginsWith($name, 'player')){
return $this->belongsTo(Player::class, $name.'_id');
}
}
there might be other ways too, but I suspect they'd be a little messier.
I have three models Class, Students and Studentinfo. Class and students are in a One to Many relationship and Sudents and studentinfo are in a one to one relationship.
While getting students from certain Class I get a list of data in an array.
What is the best way to get data from studentinfo for each student in the array?
I am trying to get this data in json format.
You'd set up relationships like the following on the models, the important one being the hasManythrough relation:
// Class.php
public function students() {
return $this->hasMany(Student::class);
}
public function studentInfo()
{
return $this->hasManyThrough(StudentInfo::class, Student::class);
}
// Student.php
public function studentInfo() {
return $this->hasOne(StudentInfo::class);
}
public function classes() {
return $this->belongsToMany(Class::class);
}
// StudentInfo.php
public function student() {
return $this->belongsTo(Student::class);
}
... you may cast a model or collection to a string, which
will automatically call the toJson method on the model or collection:
$json = (string)$class->studentInfo;
Laravel Docs: Serialization
So i've got two models, Client and Project, and Client has a hasMany relationship with projects. I'm just trying to add the project count for the client into the JSON response but I can't get it to work.
My controller just returns all the projects;
public function index(Client $client)
{
return $client->all();
}
And my model contains the below;
protected $appends = ['client_projects_count'];
/**
* Create a relationship between this client and it's projects
*/
public function clientProjects() {
return $this->hasMany('App\Project');
}
public function getClientProjectsCountAttribute()
{
return $this->withCount('clientProjects');
}
This just adds client_projects_count to the response but it's an empty array. I'm close, if I dd($this->withCount('clientProjects')->get()) I can see the client_projects_count with the correct count, but if I remove the dd and keep the ->get() then I get an internal server error.
Also, it is possible to only load this for the index() method rather than every one?
From the Documentation
$clients = Client::withCount('projects')->get();
foreach ($clients as $client) {
echo $client->projects_count;
}
So I managed to resolve it myself, although I'm sure their must be a nicer way.
Client.php
protected $appends = ['client_projects_count'];
/**
* Create a relationship between this client and it's projects
*/
public function clientProjects() {
return $this->hasMany('App\Project');
}
public function clientProjectsCount()
{
return $this->clientProjects()->selectRaw('client_id, count(*) as aggregate')->groupBy('client_id')->get();
}
public function getClientProjectsCountAttribute()
{
return isset($this->clientProjectsCount()[0]) ? $this->clientProjectsCount()[0]->aggregate : 0;
}
I have a C# class like below in an app, and looking at ways to refactor it.
The Send method does not exist in the class. This is the solution that I came up with.
There will be more email types in the future.
I don't know whether I can apply the SOLID Open/Closed principle here because adding a new emailtype require this class to be modified.
The Consumer of this service should not be concerned about the business logic, but just to know only the new emailType and the customerId. The consumer of the EmailService knows only what type of email to be sent and the customerId.
class EmailService
{
Send(int emailType, int customerId)
{
switch(emailType)
{
case 1: SendSignupEmail(customerId);
break;
case 2: SendOrderEmail(customerId);
break;
case 3: SendCancellationEmail(customerId);
break;
}
}
SendSignupEmail(int customerId);
SendOrderEmail(int customerId);
SendCancellationEmail(int customerId);
}
Strategy pattern requires you to encapsulate BEHAVIORS in order to keep your classes atomic and designed for a single purpose.
What you should do is (I'll write it in Java, but must be very very similar in C#):
interface Message {
void send(int customerId);
}
class SignupMessage implements Message {
// here you implement send method with specifics of signup behavior
}
class OrderMessage implements Message {
// here you implement send method with order specifics
}
class CancellationMessage implements Message {
// here you implement send method with cancellation specifics
}
class EmailService
{
void send(Message message, int customerId) {
message.send(customerId);
}
}
One can also argue that sending logic (connecting to POP server and sending mail) is not related to message itself. Code that remains generic should not be reimplemented, so I think this version makes a lot more sense:
interface Message {
void getMessage(int customerId);
// I've assumed only messages are different between message types
}
// other classes are the same as above (only implementing "getMessage" this time)
class EmailService {
void send(Message message, int customerId) {
string msg = message->getMessage(customerId);
// what follows next is logic to send bessage
}
}
You can replace the switch with a Dictionary and some configuration external to the class:
Define a new interface that represents the signature of the send methods:
interface ISendEmail
{
Send(int customerId);
}
For each send method, create a class that represents the send method.
class SendSignupEmail : ISendEmail
{
public Send(int customerId){}
}
class SendOrderEmail : ISendEmail
{
public Send(int customerId){}
}
class SendCancellationEmail : ISendEmail
{
public Send(int customerId){}
}
These are the email strategies.
Now EmailService can become only a means by which emailTypes are routed to the correct implementation, and it need never change for new emailTypes (OCP).
public interface IEmailService
{
void Send(int emailType, int customerId);
}
class EmailService : IEmailService
{
private readonly Dictionary<int, SendEmail> senders = new Dictionary<int, SendEmail>();
public Send(int emailType, int customerId)
{
SendEmail email;
if (senders.TryGetValue(emailType, out email)) //replaces the switch
{ //found the email type, delegate the sending to the registered instance
email.Send(customerId);
}
else
{
//unregistered email type, this is like a default case in a switch
}
}
public Register(int emailType, SendEmail sender)
{
senders.Add(emailType, sender);
}
}
Then at one point in your system you can create this service and register the email implementations:
var emailService = new EmailService();
emailService.Register(1, new SendSignupEmail());
emailService.Register(2, new SendOrderEmail());
emailService.Register(3, new SendCancellationEmail());
IEmailService iEmailService = emailService;
You should reuse this implementation and pass the same instance to the clients (DIP) as an IEmailService. Use of the interface here is ISP, because they do not require (and must not use) the classes Register method.
So as you can see, a new email implementation will just be a new class and a new registration line, achieving OCP:
emailService.Register(4, new SendSomeNewEmail(serviceItDependsOn));
Notice serviceItDependsOn, because I am using DIP I can inject extra services, or maybe an email template. Lots of additional complexity required by a new email can be handled without modifying either the client or EmailService.
This differs from usual examples of strategy pattern because of the routing to the correct strategy, but it is still externalizing the work behind an interface and the strategy implementations are supplied to the class. I think those are the key components so I would still classify this as the strategy pattern.
I have two models, Threads and Leads.
I'm trying to return the lead with the threads as a JSON object but all I am getting is a leads field that is null.
Threads Model;
public function leads()
{
return $this->belongsTo('Leads');
}
Leads Model;
public function threads()
{
return $this->hasMany('Threads');
}
ThreadsController;
public function getLead($id=null)
{
$thread = Threads::thread($id)->with('leads')->get();
return Response::json($thread)->setCallback(Input::get('callback'));
}
Instead of with(), try to load() them:
$thread = Threads::thread($id)->load('leads')->get();
Also, a note on your namings: your Threads model's leads() function should be called lead() because a Thread got only one Lead (thats why you used belongsTo()), but this is only for readibility.