laravel BelongsTo relationship with different databases not working - laravel

I've seen in several places to "stay away" from this, but alas - this is how my DB is built:
class Album extends Eloquent {
// default connection
public function genre() {
return $this->belongsTo('genre');
}
and the Genre table:
class Genre extends Eloquent {
protected $connection = 'Resources';
}
My database.php:
'Resources' => array(
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'resources',
'username' => 'user',
'password' => 'password',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
'mysql' => array(
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'my_data',
'username' => 'user',
'password' => 'password',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
and when I try to run
Album::whereHas('genre', function ($q) {
$q->where('genre', 'German HopScotch');
});
it doesn't select properly (doesn't add the database name to the table "genres"):
Next exception 'Illuminate\Database\QueryException' with message 'SQLSTATE[42S02]: Base table or view not found: 1146 Table 'my_data.genres' doesn't exist
Its important to note that this works perfectly:
Album::first()->genre;
Update
The best I've found so far is to use the builder's "from" method to specifically name the correct connection.
I've discovered that the builder inside the query can receive "from"
Album::whereHas('genre', function ($q) {
$q->from('resources.genres')->where('genre', 'German HopScotch');
});
This is a decent solution but it requires me to dig in the database php and find a good way to get the proper table and database name from the relation 'genre'.
I will appreciate if anyone else can build on this solution and make it more general.

Solution for laravel v5.7 and above
class Album extends Eloquent {
// default connection
public function genre() {
return $this->setConnection('Resources')->belongsTo('genre');
}
...
}

This is the way it worked for me:
In my .env and config/database.php i have defined my other connection => How to use multiple databases in Laravel
I updated my model this way:
class MyOtherDBModel extends Model
{
protected $table = 'tablename';
protected $connection = 'mysql2';
public function __construct(array $attributes = [])
{
$this->table = env('DB_DATABASE_2').'.'.$this->table;
parent::__construct($attributes);
}
}
class MyModel extends Model
{
public function myOtherModel()
{
return $this->belongsTo(MyOtherDBModel::class, 'field', 'field');
}
}
Now i can call
$query = MyModel::whereHas('myOtherModel');

This is my own solution and it works in general for me but its mega-complicated.
I'm using the builder "from" method to set the table and database correctly inside the subquery. I just need to pass the correct information inside.
Assume the subquery can be as complicated as "genres.sample" or even deeper (which means albums has a relation to genres, and genres has a relation to samples)
this is how
$subQuery = 'genres.samples';
$goDeep = (with (new Album));
$tableBreakdown = preg_split('/\./', $subQuery); // = ['genres', 'samples']
// I recurse to find the innermost table $album->genres()->getRelated()->sample()->getRelated()
foreach ($tableBreakdown as $table)
$goDeep = $goDeep->$table()->getRelated();
// now I have the innermost, get table name and database name
$alternativeConnection = Config::get("database.connections." . $goDeep->getConnectionName() . ".database"); // should be equal to the correct database name
$tableName = $goDeep->getTable(); // I have to use the table name in the "from" method below
Album::whereHas($subQuery, function ($q) use ($alternativeConnection, $tableName) {
$q->from("$alternativeConnection.$tableName");
$q->where(....... yadda yadda);
});
tl:dr;
Album::whereHas('genres', function ($q) {
$q->from('resources.genres')->where(....);
});

It looks like Eager Loading will do what you want to do
Album::with(['genre' => function ($q) {
$q->connection('Resources')
->where('genre', 'German HopScotch');
}]);

you should clone before otherwise you're changing the default connection of the model. it creates side effect.
class Album extends Eloquent {
public function genre() {
$newResource = clone $this;
return $newResource->setConnection('Resources')->belongsTo('genre');
}
}

Add the connection variable with the default connection on the genre model:
protected $connection = 'mysql';
I had some problems with the relationships by not adding this.

I was facing the same issue on Laravel 5.6. On a Many-to-Many scenario, and supposing the connection from my ModelA was the default one, what I did was the following:
1.- Prefix the schema name in the relationships:
// From ModelA and default connection (a.k.a connection1)
$this->belongsToMany('ModelB', 'schema.pivot-table-name');
// From ModelB and connection2
$this->belongsToMany('ModelA', 'schema.pivot-table-name');
2.- Overwrite connection parameter within the ModelB class and also specify the schema as a prefix in the overwritten $table attribute e.g.
protected $connection = 'connection2';
protected $table = 'connection2-schema-name.table';
3.- In case of requiring a custom behavior for the pivot table, what I did was just to implement the required model and specify it via the ->using('PivotModel'); function on the models relationships (as stated in the documentation). Finally I did the same as in the point 2 above, but on the pivot model
I haven't tried it yet, but I guess the same can be done for other kind of relationships, at least for the basic ones (One-to-One, One-to-Many, etc)

Just in case someone reaches here.
When you are fetching data using a relationship from different database connections, make sure that your related table has the $connection property defined even if it's the default connection.
Account Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Account extends Model {
protected $connection = 'different_connection';
public function verifiedBy()
{
return $this->belongsTo(User::class, 'verified_by');
}
}
User Model:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable {
// this one uses the default - `database.default` connection
public function approved()
{
return $this->hasMany(Account::class, 'verified_by');
}
}
As soon as I do Account:find($id)->verifiedBy, it will throw error. Look at the database name and you'll find. But if you do, User::find($id)->approved, it will work fine, because the Account model has the connection defined. And not the vice-versa.
So, just to be safe, if you deal with multiple database connections, put the $connection property in the models.
Laravel's implementation

I had the same issue when relationship wasn't working off the model connection.
My solution was to override the belongsToMany method on the model trying to establish. See example below.
<?php
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class ConnectionModel extends Model
{
/**
* Override method to allow inheriting connection of parent
*
* Define a many-to-many relationship.
*
* #param string $related
* #param string $table
* #param string $foreignKey
* #param string $otherKey
* #param string $relation
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany|BelongsToMany
*/
public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null)
{
// If no relationship name was passed, we will pull backtraces to get the
// name of the calling function. We will use that function name as the
// title of this relation since that is a great convention to apply.
if (is_null($relation)) {
$relation = $this->getBelongsToManyCaller();
}
// First, we'll need to determine the foreign key and "other key" for the
// relationship. Once we have determined the keys we'll make the query
// instances as well as the relationship instances we need for this.
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
// get connection from parent
$instance->setConnection(parent::getConnectionName());
$otherKey = $otherKey ?: $instance->getForeignKey();
// If no table name was provided, we can guess it by concatenating the two
// models using underscores in alphabetical order. The two model names
// are transformed to snake case from their default CamelCase also.
if (is_null($table)) {
$table = $this->joiningTable($related);
}
// Now we're ready to create a new query builder for the related model and
// the relationship instances for the relation. The relations will set
// appropriate query constraint and entirely manages the hydrations.
$query = $instance->newQuery();
return new BelongsToMany($query, $this, $table, $foreignKey, $otherKey, $relation);
}
}

1: This is my .env with two database connection
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=foreign_key_test
DB_USERNAME=root
DB_PASSWORD=
DB_CONNECTION_SECOND=mysql
DB_HOST_SECOND=127.0.0.1
DB_PORT_SECOND=3306
DB_DATABASE_SECOND=foreign_key_test_2
DB_USERNAME_SECOND=root
DB_PASSWORD_SECOND=
2: in my config/database.php
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mysql2' => [
'driver' => env('DB_CONNECTION_SECOND'),
'host' => env('DB_HOST_SECOND', '127.0.0.1'),
'port' => env('DB_PORT_SECOND', '3306'),
'database' => env('DB_DATABASE_SECOND', 'forge'),
'username' => env('DB_USERNAME_SECOND', 'forge'),
'password' => env('DB_PASSWORD_SECOND', ''),
'unix_socket' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
],
3: State model from other database
class State extends Model
{
use HasFactory;
protected $table = 'foreign_key_test_2.states';
protected $connection = 'mysql2';
protected $primaryKey = 'id';
protected $fillable = [
'name'
];
}
4: Country model from 1st database where i made my relation with other database table
class Country extends Model
{
use HasFactory;
protected $connection = 'mysql';
protected $table = 'countries';
protected $primaryKey = 'id';
protected $fillable = [
'name',
'state_id'
];
public function state()
{
return $this->belongsTo(State::class);
}
}
5: Final step is my route
Route::get('city', function(){
return $country = Country::with('state')->get();
});
6: this is working fine for me. Let me know with your experience.

I found a really good article for this here: http://fideloper.com/laravel-multiple-database-connections
You basically have to specify your two connections in your config file like so:
<?php
return array(
'default' => 'mysql',
'connections' => array(
# Our primary database connection
'mysql' => array(
'driver' => 'mysql',
'host' => 'host1',
'database' => 'database1',
'username' => 'user1',
'password' => 'pass1'
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
# Our secondary database connection
'mysql2' => array(
'driver' => 'mysql',
'host' => 'host2',
'database' => 'database2',
'username' => 'user2',
'password' => 'pass2'
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
),
);
So your two connections are aliased to mysql and mysql2.
Then you can tell eloquent which 'alias' to use like so:
<?php
class SomeModel extends Eloquent {
protected $connection = 'mysql2';
}
Then you can setup your relationships like normal.
tl;dr: Basically instead of specifying the table name as $connection in eloquent, specify the connection alias in your configuration and it should work.

I used the following method to use the default connection.
use Illuminate\Database\Eloquent\Model as MainModel;
use Illuminate\Support\Facades\Config;
class BaseModel extends MainModel
{
function __construct(array $attributes = [])
{
// laravel bug
// in belongsTo relationship, default connection not used
$this->connection = Config::get('database.default');
parent::__construct($attributes);
}
}

To start change 'Resources' in database.php by 'resources', will be better !
I'm curious, can you try that ?
Album::whereHas('genre', function ($q) {
$q->setConnection('resources')->where('genre', 'German HopScotch');
});

Related

Laravel-Auditing is not working without any errors

I've recently installed this package and configured everything with guide but some how it's not working!
By it's not working I mean it's not adding anything to database. I really don't know what is wrong with my configs but I've checked everything with guide 3 times and everything is correct but... I don't know
config/audit.php:
<?php
return [
'enabled' => env('AUDITING_ENABLED', true),
'implementation' => OwenIt\Auditing\Models\Audit::class,
'user' => [
'morph_prefix' => 'user',
'guards' => [
'web',
'api',
],
],
'resolver' => [
'user' => OwenIt\Auditing\Resolvers\UserResolver::class,
'ip_address' => OwenIt\Auditing\Resolvers\IpAddressResolver::class,
'user_agent' => OwenIt\Auditing\Resolvers\UserAgentResolver::class,
'url' => OwenIt\Auditing\Resolvers\UrlResolver::class,
],
'events' => [
'created',
'updated',
'deleted',
'restored',
'gold_mailed' => 'goldMailed',
'invited' => 'clientInvited',
],
'strict' => false,
'timestamps' => false,
'threshold' => 0,
'driver' => 'session',
'drivers' => [
'eloquent' => [
'table' => 'audits',
'connection' => null,
],
],
'console' => true,
];
My model that I want to audit:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;
use App\Models\Expansion;
use App\Models\Audit;
class Setting extends Model implements Auditable
{
protected $table = 'settings';
use \OwenIt\Auditing\Auditable;
protected $fillable = [
'expansion_id', 'season', 'advertiser_app', 'pvp_app', 'raid_app', 'version'
];
protected $auditInclude = [
'expansion_id', 'season', 'advertiser_app', 'pvp_app', 'raid_app', 'version'
];
public function Expansion()
{
return $this->hasOne(Expansion::class, 'id', 'expansion_id');
}
}
web.php:
Route::post('/setting' , 'Admin\SuperAdminController#saveSetting')->middleware('superadmin')->name('admin_save_setting');
Controller:
public function saveSetting(Request $request)
{
$sql = Setting::where('id', 1)->update([
'expansion_id' => $request['expansion_id'],
'season' => $request['season'],
'advertiser_app' => $request['advertiser_app'],
'pvp_app' => $request['pvp_app'],
'raid_app' => $request['raid_app'],
'version' => $request['version']
]);
if ($sql) {
toastr()->success('Settings successfully updated.');
return redirect()->back();
}
toastr()->error('Something went wrong!');
return redirect()->back();
}
I don't know what infos do you need but I think this is enough
I think my problem is with "driver" in config file , I don't know if that's correct or not
[UPDATED]
Based on the controller code you showed, it didn't work because your code is being called using Builder style, and the package only works when it is called using Eloquent style.
Documentation link
So, maybe you need to change your code to:
$setting = Setting::where('id', 1)->firstOrFail();
$setting->update([
'expansion_id' => $request['expansion_id'],
'season' => $request['season'],
'advertiser_app' => $request['advertiser_app'],
'pvp_app' => $request['pvp_app'],
'raid_app' => $request['raid_app'],
'version' => $request['version']
]);
now I have another problem -_-
this is my controller:
$sql = Raid::findOrFail($request['id']);
$sql = $sql->update($request->all());
I have a array in my table , after update value will be like this:
"{\"Plate\":0,\"Cloth\":0,\"Mail\":0,\"Leather\":0}"
but it should be:
{"Plate":"0","Cloth":"0","Mail":"0","Leather":"0"}
so I will get an error
before this , I was updating like this and it was ok:
$sql = Raid::where('id', $request['id'])->update($request->all());
and this is my mode (traders and class_traders is fields that I have problem with):
use SoftDeletes;
use \OwenIt\Auditing\Auditable;
protected $table = 'raid';
protected $dates = ['date_and_time','deleted_at'];
protected $fillable = [
'admin_id', '....
];
protected $casts = [
'bosses' => 'array',
'traders' => 'array',
'class_traders' => 'array',
'boosters' => 'array',
];

Laravel with multiple database

I have laravel project used for apis(Passport). It uses multiple databases one database stores user information and the database name for that user. I created a BaseController, all the other controllers extends from this controller. But I am not able to fetch Auth::id(); from the BaseController. How can I get the Auth::id in the constructor?
class BaseController extends Controller
{
protected $companySchema;
public function __construct() {
$user = \App\User::where('user_id', Auth::id())->first();
$company = $user->company;
$this->companySchema = $company->cmp_Schema;
}
}
After laravel 5.3.4 you can't use Auth::user() in the constructor because the middleware isn't run yet.
class BaseController extends Controller {
public function __construct() {
$this->middleware(function ($request, $next) {
$this->companySchema = Auth::user()->company->cmp_Schema;
return $next($request);
});
}
}
try this code and tell me if it works
You cannot use Auth in constructor, because middleware is not run yet. its from 5.3
check this link : https://laravel.com/docs/5.3/upgrade#5.3-session-in-constructors
First you should setup a connection in config/database.php:
'my_new_connection' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => $your_db_name,
'username' => $your_db_username,
'password' => $your_db_password,
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
Then you can change default database connection dynamically by this:
Config::set('database.default', 'my_new_connection');
You should first call
$user = Auth::user();
which is returning you the current user model associated, which is normally User. Then following should work:
$user->id

Cannot use DB::select in public folder in laravel

TestModel.php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;;
class TestModel extends Model
{
protected $table = 'test';
public static function index() {
$configs = DB::select('select * from tests');
}
}
This is my code. I have a file in /public folder name Test.php and want to use DB. But when i call the index() function in Test.php, it returns error.
PHP Fatal error: Uncaught RuntimeException: A facade root has not been set
Test.php saved in public folder
use App\Http\Models\TestModel;
$configs = TestModel::index();
Please help. Thank you!
When you are using anything in public folder without calling index.php it will not initiate the database so to overcome this issue :
/**
* Using For Database Configuration
*/
define('DB_TYPE', 'mysql');
define('DB_USER', 'username');
define('DB_HOST', 'localhost');
define('DB_PORT', '3306');
define('DB_PASSWORD', '');
define('DB_NAME', 'db-name');
$test = new TestModel;
$test->addConnection([
'driver' => DB_TYPE,
'host' => DB_HOST,
'port' => DB_PORT,
'database' => DB_NAME,
'username' => DB_USER,
'password' => DB_PASSWORD,
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
'engine' => null,
]);
$test->setAsGlobal();
$test->bootEloquent();
$configs = $test->index();
Also, you have to replace one line in TestModel.php
replace use Illuminate\Support\Facades\DB;; with use Illuminate\Database\Capsule\Manager as DB;
Now when you run this command this will be working for you.

Testing a controller method that uses related models in laravel

I am working in laravel-lumen. I have two models. An Organization model and an Apikey model corresponding to an organizations and an apikeys table. The column organization_id in the apikeys table is a foreign key referring to the id field of the organizations table.
The model for organizations looks like
<?php
namespace App;
use App\Apikey
use Illuminate\Database\Eloquent\Model;
Class Organization Extends Model {
public $table = 'organizations';
public $fillable = [
'name',
'contact_name',
'contact_phone',
'contact_email',
'address1',
'state',
'city',
'zip',
'country'
];
public function apikeys()
{
return $this->hasMany('App\Apikey');
}
}
The apikeys model looks like this
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
Class Apikey Extends Model {
public $table = 'apikeys';
public $fillable = [
'key',
'secret',
'organization_id',
'permissions'
];
}
organization_id in apikeys is a foreign key in the organizations table that refers to the id field of organizations table.
Now I have a controller that generates the api key given an organization_id and the permissions and fills the apikeys table. It looks like this
<?php
use App\Http\Controllers\Controller;
use App\Apikey;
use Illuminate\Http\Request;
public function generateApiKeyGivenOrganizationId(Request $request)
{
$data = $request->all();
// code for generating api key.
$dd = [
'key' => 'generated encrypted key',
'secret' => 'secret',
'organization_id' => $data['organization_id'],
'permissions' => $data['permissions']
];
$xx = Apikey::create($dd);
return response()->json(['status' => 'ok', 'apikey_id' => $xx->id]);
}
}
I want to test this code. I created two model factories like this.
$factory->define(Organization::class, function ($faker) use ($factory) {
return [
'name' => $faker->name,
'contact_name' => $faker->name,
'contact_phone' => '324567',
'contact_email' => $faker->email,
'address1' => 'xxx',
'state' => 'Newyork',
'city' => 'Newyork',
'country' => 'USA'
];
});
$factory->define(Apikey::class, function ($faker) use ($factory) {
return [
'key' => 'xxx',
'secret' => 'xxxx',
'permissions' =>'111',
'organization_id' => 7
});
My testing function looks like this.
public function testApiKeyGeneration ()
{
factory(App\Organization::class)->create()->each(function($u) {
$data = [
'organization_id' => $u->id,
'permissions' => '111'
];
$this->post('/createapikeyfororg' , $data)
->seeJson(['status' => 'ok']);
});
}
The controller works perfectly. It is only in the testing I am having problems. The url '/createapikeyfororg' is the url that invokes the controller method generateApiKeyGivenOrganizationId(). Is this testing procedure correct? I am yet to try it out and I am asking this question on a Saturday because I am really in a hurry. I am a total novice at testing and I am in a hurry and any help would be appreciated.

Login Auth in laravel customsize (SOLUTION)

Attempt to login in Laravel with custom data: num Id, TipoId, password Auth, but I always returns false, not to be configured to accept different data to email and password:
$tipoId=Input::get('tipoId');
$numId=input::get('numId');
$password=Input::get('password');
$userdata = array('tipoId' => Input::get('tipoId'), 'numId' => Input::get('numId'), 'password' => Input::get('password') );
if (Auth::attempt($userdata)) {
}else{
return $userdata;
}
Model
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class Login extends Eloquent implements UserInterface, RemindableInterface{
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'Logins';
}
Auth.php
return array(
'driver' => 'eloquent',
'model' => 'Login',
'table' => 'Logins',
'reminder' => array(
'email' => 'emails.auth.reminder',
'table' => 'password_reminders',
'expire' => 60,
),
Does not leave me any error but every time I try to login, I always returns false, not this doing wrong?
$2y$10$FQobU84gHR7ee5VfwUOHZuTFNd6UdLs/Izr9N
And there we have the problem. This hash is only 44 characters long, that almost certainly means it got truncated. Your database field for the password has to be 60 characters long. Change that and update the hash.

Resources