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

axtg's avatar
Level 5

Bridging users, companies and transactions

Okay, so I've tried my best to search the web for a proper answer, but have not been able - due to a lack of the proper terms I assume - to find the best solution. Here is my situation, I hope someone can help me in the right direction.

I have users, companies, transactions and balances tables. Users can make transactions to companies or to others users.

Ideally therefore I'd like to have a transactions table that looks something like:

id, entity_id, amount, type(credit, debit) and timestamp
1,1,100,"C",2018-01-01
2,1,20,"D",2018-02-01
3,2,20,"C",2018-02-01
4,1,10,"D",2018-03-01
5,3,10,"C",2018-03-01

Here:

  • Entity_id 1 top-ups (Credit) 100 on 01-01-2018
  • Entity_id 1 transfers (Debit) 20 on 01-02-2018
  • Entity_id 2 receives (Credit) 20 on 01-02-2018
  • Entity_id 1 transfers (Debit) 10 on 01-03-2018
  • Entity_id 3 receives (Credit) 10 on 01-03-2018

Where entity_id refers to (e.g.) an entities table that looks like:

id, user_id, company_id
1,1,NULL
2,NULL,1
3,2,NULL

Now my questions:

  1. The entities table feels "weird" to me (because of the NULLs), is this the best way?
  2. I am unsure how to get the Collection of all transactions for user_id=1 (e.g. App\User::find(1)->transactions->get())
  3. However 2 is done, could I then still chain a transactions_details table if present?

Perhaps 2 and 3 is what the "Polymorphic Relations" are for, but the documented example of Posts, Users, Comments and Tags didn't speak to me for my situation.

Thank you, Xander.

0 likes
3 replies
xmarks's avatar
xmarks
Best Answer
Level 8

If what you want to achieve is this command:

App\User::find(1)->transactions->get();

Then why don't you structure your Database like:

  • companies table => id | ....
  • users table => id | company_id | ...
  • transactions table => id | user_id | ...

Then add relations:

Company Model:

public function users() { return $this->hasMany(User::class); }

User Model:

public function company() { return $this->belongsTo(Company::class); }

public function transactions() { return $this->hasMany(Transaction::class); }

Transaction Model:

public function user() { return $this->belongsTo(User::class); }

Now you can have:

  • Eager Load Relation: $user = App\User::find(1)->with('transactions')->get();
  • Then the results are: $user->transactions;

Edit

if 1 Transaction has 1 TransactionDetail, then this table is not needed I think. Simply add the extra fields to the transactions table.

For Transaction Details, you can then have:

  • transaction_details table => id | transaction_id | ... Transaction Model:
public function transactionDetails() { return $this->hasMany(TransactionDetail::class); }

TransactionDetail Model:

public function transaction() { return $this->belongsTo(Transaction::class); }

Then the one you had before becomes:

  • Eager Load Relation: $user = App\User::find(1)->with('transactions.transactionDetails')->get();
  • Then the results are: $user->transactions; To get the details:
@foreach($user->transactions as $transaction)
    {{ $transaction->transactionDetails->id; }}
@endforeach
axtg's avatar
Level 5

Hi xmarks,

Thanks for a quick reply. You make it seem so easy. Having somebody look at this cold, certainly helps.

1. On linking company with user Yes, I can add a company_id to the users table instead of my current belongsToMany intermediate table. One "downside" then is that not every user is part of a company, so the company_id will be NULL for some (if not most). But I don't see any actual objections to do so. Or?

2. Getting company transactions Given this I assume that - they are new for me - I can get the transactions for a company through a hasManyThrough relation (in Company model):

public function transactions() 
{ 
    return $this->hasManyThrough(
        Transaction::class, 
        User:class,
        'company_id',
        'user_id',
        'id',
        'id',
    );
}
 
$companyTransactions = App\Company::find(1)->with('transactions')->get();

3. Getting transaction details Theoretically I can then, through the hasManyThrough, get to the details, also for a company.

$companyTransactionDetails = App\Company::find(1)->with('transactions.transactionDetails')->get();

Or indeed, but I'll have a think about that, add details to the same row in Transactions if indeed I expect a 1:1 relation on these tables.

Thanks.

xmarks's avatar

1.

I think that the only case where you should require a PivotTable company_user is if a User can belong to more than 1-company. Otherwise, it wouldn't hurt to keep the relation on the users Table.

2.

Sure, if that works for you. I do not have much experience with hasManyThrough as I have yet to make use of it, but I checked the documentation and it seems correct. You could probably simplify it since you are currently using Laravel Naming Conventions:

public function transactions()
    {
        return $this->hasManyThrough(Transaction::class, User::class);
    }

3.

Again same with the relation between users and companies. If you are certain that the relation is 1:1, then you should keep things in the same Table. I think systems (Servers, Storage and Programs) are now powerful enough to not having to worry about null-assigned fields taking up too much space.

I would think that you owe it to yourself and whoever will take care of your code later to keep things as simple and as easy to manage as possible. But again this would be a personal opinion.

I would only use a PivotTable (or Intermediate Table) for true cases of Many-To-Many Relations, like "Tags / Categories etc..." where I would check if the data has already been used before:

  • If Yes => assign the same data
  • If No => create the new data and assign it / Or Data does not exist exception

Then this data, having the same ID, can be used to fetch all different Models where it has been assigned to. (Like for example, creating a Search for Posts, from certain Categories or Tags / Or Finding all Companies of a User, if more than 1 in your example)

Please or to participate in this conversation.