Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

TNGDWN's avatar

Message to User randomizer

Hello! I'm trying to achieve something fairly simple, but I'm not sure if it's done in a clean way. I have 3 tables. The users table, the message table and a message_user table (many to many table). The message_user table contains just 2 foreign keys (1 from the users table, and 1 from the message table) as a composite key. The idea is that a user can fill in a message form, select the amount of people the user wants to send the message to and it then sends the message to random users (it does this by attaching the message to let's say; 10 random user id's in the message_user table).

My controller looks like this:

/**
     * Store the new message to the database.
     * 
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        // Create a new Message model, insert the data and save it
        $message = new Message($request->except(["amount"]));
        $message->user_id = Auth::user()->id;
        $message->save();
        
        // Randomize the message
        $amount = (int) $request->amount;
        $this->messageRandomizer($message, $amount);

        // Redirect to the root url (homepage)
        return redirect("/");
    }

The messageRandomizer method looks like this:

private function messageRandomizer(Message $message, $amount)
{   
    $iteration = 0;
    // While loop because it must have looped the amount of times of the value of $amount (like 10 or so)
        while ($iteration < $amount) {
                // Pick a random row from the users table. Returns a User Model and save it's id to a variable.
        $randomId = User::all()->random(1)->id;
        // Attach the message to a random user id if the $randomId is something different than your own user_id (because you can't send/attach the message to yourself). Else loop again.
                if ($randomId !== Auth::user()->id) {
        // Attach the current message with the randomized id to the message_user table. (userPivot() returns a belongsToMany())
                $message->userPivot()->attach($randomId);
                // Only increment the current count if the current message got assigned. This way we're 100% sure that we attach the current message the amount of times of the value of $amount. Like 50 times. Or 10 times.
                $iteration++;  
            }
        }
    }

It looks pretty solid to me, but is there even a cleaner way to do this? I also have another question; The code works fine (also performance wise) when I'm not using composite keys. But the reason I now use composite keys is because I don't want to attach the same message to the same user more than 1 time and instead want the rows to be unique (no duplicates). I know I can fix this problem with a try and catch block that whenever I catch the Integrity constraint violation: 1062 Duplicate entry '12-9' for key 'PRIMARY' error, I just run through the loop again by using a continue; statement, but this doesn't feel like a good practice since the loop just keeps looping over and over until the random number generation is in our favor and finally attaches the message to all intended users.

Thanks in advance!

Edit: Wrapping the code in try and catch blocks makes it a lot worse performance-wise.

0 likes
5 replies
anonymous's avatar

I would add a check for duplicate ID's in your randomizer.

example:


$iteration = 0, $used =[];
    // While loop because it must have looped the amount of times of the value of $amount (like 10 or so)
        while ($iteration < $amount) {
                // Pick a random row from the users table. Returns a User Model and save it's id to a variable.
        $randomId = User::all()->random(1)->id;
        // Attach the message to a random user id if the $randomId is something different than your own user_id (because you can't send/attach the message to yourself). Else loop again.
                if ($randomId !== Auth::user()->id && !in_array($randomId, $used) {
    // Add id to used list
            array_push($used, $randomId);
        // Attach the current message with the randomized id to the message_user table. (userPivot() returns a belongsToMany())
                $message->userPivot()->attach($randomId);
                // Only increment the current count if the current message got assigned. This way we're 100% sure that we attach the current message the amount of times of the value of $amount. Like 50 times. Or 10 times.
                $iteration++;  
            }
        }

1 like
TNGDWN's avatar

Looks good! Code works, but I feel like it's a whole lot slower than it was before (but the response times are quite slow whatsoever, and I don't know why). I sometimes get Maximum execution time of 30 seconds exceeded

anonymous's avatar

The fastest solution would be to write your query to your specific database and use the db specific calls.


private function messageRandomizer(Message $message, $amount)
{   

    // Get DB driver type
    $db_driver = \DB::connection()->getDriverName();

    // If mysql or sqlite then set approiate call
    if($db_driver == 'sqlite') $db_random = 'RANDOM()';
    elseif($db_driver == 'mysql') $db_random = 'RAND()';

    // make db call
    $randomUserIds = User::where('id','!=',Auth::user()->id)->orderByRaw($db_random)->take($amount);

    // loop through ids and add to pivot table
    foreach ($randomUserIds as $randomId) {

        // push to Pivot table..
        $message->userPivot()->attach($randomId);

    }

}

1 like
TNGDWN's avatar
TNGDWN
OP
Best Answer
Level 4

Thanks anonymous. You helped me on the right track. I've used the same concept, but tweaked it a little bit for my own needs.

This is the final code (and it is actually pretty clean!)

private function messageRandomizer(Message $message, $amount)
{   
    // Get a collection of all the users except for the user that's currently logged in
    $userCollection = User::where("id", "!=", Auth::user()->id)->get();

    // If there are less records of users in the database than the amount of people the message gets send to,
    // reduce the assignment amount to the max number of records excluding yourself.
    $amount = User::count() < $amount ? (User::count() - 1) : $amount;

    // Get an x amount of random records. For each User generated, attach it's id to the message.
    foreach ($userCollection->random($amount) as $randomUser) {
        $message->userPivot()->attach($randomUser->id);
    }
}
dominicyberx's avatar

I just came across your post. So, to prevent duplicate entries in your message_user table, it's better to utilize a unique constraint on the composite key columns rather than relying on try-and-catch blocks. This way, the database will automatically reject any attempts to insert duplicate entries, saving you from the continuous looping in your code. Regarding your code's performance, have you considered using efficient indexing and optimizing your database queries? It might help improve the overall speed.By the way, I came across this website called FlipSimu (https://flipsimu.com/dice-roller/roll-d4/) during my online browsing adventures. They have a nifty d4 roller feature that allows you to roll four-sided dice virtually. It's handy for various gaming scenarios or any situation where randomness is involved.

Please or to participate in this conversation.