1

I'm rather new to Laravel 4 and can't seem to find the right answer, maybe you can help:

A User in our application can have many Accounts and all data is related to an Account, not a User. The account the User is currently logged into is defined by a subdomain, i.e. accountname.mydomain.com.

We added a method account() to our User model:

/**
 * Get the account the user is currently logged in to
 */
public function account()
{
    $server = explode('.', Request::server('HTTP_HOST'));
    $subdomain = $server[0];
    return Account::where('subdomain', $subdomain)->first();
}

The problem is that there is always an extra query when we now use something like this in our view or controller:

Auth::user()->account()->accountname

When we want to get "Products" related to the account, we could use:

$products = Product::where('account_id', Auth::user()->account()->id)->get();

And yet again an extra query...

Somehow we need to extend the Auth::user() object, so that the account data is always in there... or perhaps we could create a new Auth::account() object, and get the data there..

What's the best solution for this? Thanks in advance

Laurence
  • 58,936
  • 21
  • 171
  • 212
24creative
  • 709
  • 2
  • 9
  • 14
  • How about storing your model into a session – Razor May 09 '14 at 13:45
  • That was our first thought as well, but I think it's ugly if we have to use Session::get('account_id') everywhere. Plus the user can change the subdomain to another account, so the session has to be checked/updated on every request. – 24creative May 09 '14 at 14:03
  • From what you write, account() method has no relation with User, so you should not put it in User model, you can add it at the end of bootstrap/start.php and call `account()->id` ( It's not the best practices, just try and let us know if it works) – Razor May 09 '14 at 16:04

3 Answers3

0

Just set it to a session variable. This way, you can check that session variable before you make the database call to see if you already have it available.

Or instead of using ->get(), you can use ->remember($minutes) where $minutes is the amount of time you wish to keep the results of the query cached.

user1669496
  • 32,176
  • 9
  • 73
  • 65
-1

You should take a look at Eloquent relationships : http://laravel.com/docs/eloquent#relationships

It provides simple ways to get the account of a user and his products. You said that a user can have many accounts but you used a first() in your function I used a hasOne here.

Using Eloquent relationships you can write in your User model:

<?php
public function account()
{
    // I assume here 'username' is the local key for your User model
    return $this->hasOne('Account', 'subdomain', 'username');
}

public function products()
{
    // You should really have a user_id in your User Model 
    // so that you will not have to use information from the
    // user's account
    return $this->hasMany('Product', 'account_id', 'user_id');
}

You should define the belongsTo in your Account model and Product model.

With Eager Loading you will not run a lot of SQL queries : http://laravel.com/docs/eloquent#eager-loading

You will be able to use something like

$users = User::with('account', 'products')->get();

To get all users with their account and products.

Antoine Augusti
  • 1,598
  • 11
  • 13
  • I can't use hasOne, since the user can have many accounts.. There is a users_accounts table to connect users to their accounts and viceversa. We have a belongsToMany relationship that gets all the accounts the user has access to, but the problem is the account the user is currently viewing (only defined by the subdomain). We need that information available in the Auth::user() object without performing DB queries. – 24creative May 09 '14 at 13:52
-1

I think this is a good example for the purpose of Repositories.

You shouldn't query the (involved) models directly but wrap them up into a ProductRepository (or Repositories in general) that handles all the queries.

For instance:

<?php

class ProductRepository
{
    protected $accountId;

    public function __construct($accountId)
    {
         $this->accountId = $accountId;
    }

    public function all()
    {
        return Product::where('account_id', $this->accountId)->get();
    }
}

//now bind it to the app container to make it globaly available
App::bind('ProductRepository', function() {
    return new ProductRepository(Auth::user()->account()->id);
});

// and whenever you need it:
$productRepository = App::make('ProductRepository');
$userProducts = $productRepository->all();

You could group the relevant routes and apply a filter on them in order to bind it on each request so the account-id would be queried only once per repository instance and not on every single query.

Scopes could also be interesting in this scenario:

// app/models/Product.php
public function scopeCurrentAccount($query)
{
    return $query->where('account_id', Auth::user()->account()->id);
}

Now you could simply call

$products = Product::currentAccount()->get();
Community
  • 1
  • 1
Quasdunk
  • 14,944
  • 3
  • 36
  • 45