3
votes

I am using MSSQL having 2 connections named seven_ora (which a User model's table is in), and sho (which a Character model's table is in).

A user hasMany characters, and a character belongsTo user.

When I try:

Character::whereHas('User', function($q) { $q->where('gm', 1); });

It seems to use the same connection as Character. And it seems that the closure in whereHas is a Query\Builder? I expected the whereHas to use the respective set connection of whatever model it is referring to.

Is there a way to use a different connection on my whereHas closure?

Characters.php (Model)

class Character extends Base {

    /**
     * Connection used by the model
     *
     * @var string
     */
    protected $connection = 'sho';

    /**
     * Table used by the model
     *
     * @var string
     */
    protected $table = 'tblgs_avatar';

    /**
     * Fields fillable by the model
     *
     * @var array
     */
    protected $guarded = array('*');

    /**
     * Checks whether the model uses timestamps
     *
     * @var boolean
     */
    public $timestamps = false;

    /**
     * Decode the job name of the given ID
     *
     * @return  string
     */
    public function getJobName()
    {
        switch( $this->job ) {
            case 0:
                $job = 'Visitor';
                break;
            case 111:
                $job = 'Solider';
                break;
            case 221:
                $job = 'Muse';
                break;
            case 311:
                $job = 'Hawker';
                break;
            case 411:
                $job = 'Dealer';
                break;
            case 121:
                $job =  'Knight';
                break;
            case 122:
                $job = 'Champ';
                break;
            case 221:
                $job = 'Mage';
                break;
            case 222:
                $job = 'Cleric';
                break;
            case 321:
                $job = 'Raider';
                break;
            case 322:
                $job = 'Scout';
                break;
            case 421:
                $job = 'Bourg';
                break;
            case 422:
                $job = 'Artisan';
                break;
            default:
                $job = 'Untitled';
                break;
        }

        return $job;
    }

    /**
     * Rank the characters according to provided details
     *
     * @param   integer     $offset
     * @param   string      $field
     * @return  Character
     */
    public static function byTop($offset = 10, $field = null)
    {
        $characters = new static();

        if ( !is_null($field) ) $characters->orderBy($field, 'desc');

        return $characters->take($offset);
    }

    /**
     * A shortcut to access the name of the character
     *
     * @return  string
     */
    public function getNameAttribute()
    {
        return $this->txtNAME;
    }

    /**
     * A shortcut to access the level of the character
     *
     * @return  int
     */
    public function getLevelAttribute()
    {
        return $this->btLEVEL;
    }

    /**
     * A shortcut to access the job of the character
     *
     * @return  integer
     */
    public function getJobAttribute()
    {
        return $this->intJOB;
    }

    /*
    |--------------------------------------------------------------------------
    | ORM
    |--------------------------------------------------------------------------
    */

    /**
     * ORM with the [User] table
     *
     * @return  User 
     */
    public function user()
    {
        return $this->belongsTo('User', 'Account', 'txtACCOUNT');
        //$user = User::where('Account', $this->txtACCOUNT)->first();

        //return $user;
    }
}

User.php (Model)

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;

class User extends Base implements UserInterface, RemindableInterface {

    /**
     * Connection used by the model
     *
     * @var string
     */
    protected $connection = 'seven_ora';

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'userinfo';

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = array('MD5PassWord', 'password');

    /**
     * Fields guarded by the model
     *
     * @var array
     */
    protected $guarded = array();

    /**
     * Fields fillable by the model
     *
     * @var array
     */
    protected $fillable = array(
        'Account',
        'Email',
        'MD5PassWord',
        'FirstName',
        'LastName',
        'MotherLName'
    );

    /**
     * Checks whether the model uses timestamps
     *
     * @var boolean
     */
    public $timestamps = false;

    /**
     * Get the unique identifier for the user.
     *
     * @return mixed
     */
    public function getAuthIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Get the password for the user.
     *
     * @return string
     */
    public function getAuthPassword()
    {
        return $this->MD5PassWord;
    }

    /**
     * Get the token value for the "remember me" session.
     *
     * @return string
     */
    public function getRememberToken()
    {
        return $this->remember_token;
    }

    /**
     * Set the token value for the "remember me" session.
     *
     * @param  string  $value
     * @return void
     */
    public function setRememberToken($value)
    {
        $this->remember_token = $value;
    }

    /**
     * Get the column name for the "remember me" token.
     *
     * @return string
     */
    public function getRememberTokenName()
    {
        return 'remember_token';
    }

    /**
     * Get the e-mail address where password reminders are sent.
     *
     * @return string
     */
    public function getReminderEmail()
    {
        return $this->email;
    }

    /**
     * Validate input
     *
     * @param   array       $input
     * @param   integer     $id
     * @return  Validator
     */
    public function validate(array $input = array(), $id = null)
    {
        // Rules
        $username   = 'required|alpha_num|between:4,32|unique:userinfo,Account';
        $password   = 'required|between:4,48';
        $email      = 'required|email|unique:userinfo,Email';
        $mname      = 'required';

        // Unique rules
        if(!is_null($id)) {
            $unique      = ',' . $id;
            $username   .= $unique;
            $email      .= $unique;
        }

        $rules = array(
            'username'  =>  $username,
            'password'  =>  $password,
            'email'     =>  $email,
            'mname'     =>  $mname
        );

        $messages = array('mname.required' => "The security question field is required");

        Config::set('database.default', 'seven_ora'); // Set config
        return Validator::make($input, $rules, $messages);
    }

    /**
     * Change password of the user
     *
     * @param   string  $old
     * @param   string  $new
     * @return  boolean
     */
    public function changePassword($old, $new)
    {
        if(Hash::check($old, $this->MD5PassWord)) {
            $this->MD5PassWord = Hash::make($new);

            if($this->save()) return true;
        }

        return false;
    }

    /**
     * Checks if the user is a GM
     *
     * @return  boolean
     */
    public function isGM()
    {
        return ( $this->Right > 1 )
            ? true
            : false;
    }

    public function addVP($points)
    {
        $vp = $this->votePoint;
        $count = $vp->count;
        $vp->count = $count + $points;
        $vp->save();
    }

    /**
     * Since our server files does not use the typical
     * field names, this sets our username to whatever is being used
     *
     * @return  void
     */
    public function getUsernameAttribute()
    {
        return $this->Account;
    }

    /**
     * Since our server files does not use the typical
     * field names, this sets our password to whatever is being used
     *
     * @return  void
     */
    public function getPasswordAttribute()
    {
        return $this->MD5PassWord;
    }

    /**
     * A shortcut to the user's vote point count
     *
     * @return  void
     */
    public function getVpAttribute()
    {
        return $this->votePoint->count;
    }

    public function getDpAttribute()
    {
        return $this->donationPoint->count;
    }


    /*
    |--------------------------------------------------------------------------
    | ORM
    |--------------------------------------------------------------------------
    */

    /**
     * ORM with the Character model
     *
     * @return  Character
     */
    public function characters()
    {
        return $this->hasMany('Character', 'txtACCOUNT', 'Account');
        //$characters = Character::where('txtACCOUNT', $this->username)->get();

        //return $characters;
    }

    /**
     * ORM with the VotePoint model
     *
     * @return  VotePoint
     */
    public function votePoint()
    {
        return $this->hasOne('VotePoint');
    }

    /**
     * ROWM with the DonationPointmodel
     *
     * @return  DonationPoint
     */
    public function donationPoint()
    {
        return $this->hasOne('DonationPoint');
    }

    /**
     * ORM with the News model
     *
     * @return  News
     */
    public function news()
    {
        return $this->hasMany('News');
    }

    /**
     * ORM with the Slide model
     *
     * @return  Slide
     */
    public function slides()
    {
        return $this->hasMany('Slide');
    }

    /**
     * ORM with the VoteLog model
     *
     * @return  VoteLog
     */
    public function logs()
    {
        return $this->hasMany('VoteLog');
    }

}
3

3 Answers

8
votes

Prepend the database name to the $table value in class User. So assuming seven_ora is also the name of the DB:

protected $table = 'seven_ora.userinfo';
2
votes

You can also add it via a config variable in constructor to accommodate different environments

  public function __construct($attributes = array())  {
        parent::__construct($attributes); 

        $this->table = Config::get('database.connections.connection.database').'.model_table_name';
  }
0
votes

This might help:

  1. When you use Character::whereHas('User',...), the query builder creates a single DB query, so can only use one connection (and its credentials). The query is something like:
    • SELECT * FROM characters WHERE (SELECT COUNT(*) FROM users WHERE characters.user_id = user.id AND gm = 1) >= 1; [uses the connection defined on the Character model (as you have noticed)]
  2. When you refer to a relationship separately or use eager loading like Character::with('user'), there are two separate DB queries executed, each one using its respective connection (and connection credentials):
    • SELECT * FROM characters; [uses connection defined on the Character model]
    • SELECT * FROM users WHERE id IN (1, 2, 3, …); [uses connection defined on the User model]

Prepending the database name to the table name, as the solution suggests, will only work if the Character model connection has privileges to access the other database, because you are still using the Character model connection.