Laravel whereHas across two separate databases - php

Problem: I want to get Customers only if they have already placed an order for the current year. Customers and Orders are on two separate Databases (this can't change). The relationships are all setup and working correctly but when I try the following I keep getting an SQL error as it is trying to search 'orders' on the 'customers' database. Is there anyway to force Laravel to use the correct database in this scenario?
$customers = $customers->whereHas('orders', function($query){
$query->where('academic_year_id', '=', $this->current_academic_year->id);
});
$customers = $customers->orderBy('DFKEY','ASC')->get();
Order Model:
public function customer()
{
return $this->belongsTo('Customer','dfkey');
}
Customer Model:
protected $connection = 'mysql2';
public function orders()
{
return $this->hasMany('Order','dfkey','DFKEY');
}
Thanks in advance!

Late to the party but for anybody else who has a similar issue, the below should work (as long as both databases are on a single server).
Set BOTH the connection and table explicitly.
protected $connection = 'mysql2';
protected $table_name = 'mysql2.orders';
Or if you want - dynamically set the table like this:
protected $table = 'orders';
public function __construct() {
$this->table = DB::connection($this->connection)->getDatabaseName() . '.' . $this->table_name;
}
Or even
public function __construct() {
$this->table = DB::connection($this->connection)->getDatabaseName() . '.' . $this->getTable();
}

Solved this by using a filter:
public function index()
{
$customers = new Customer;
// Make sure customers are current parents of students
$customers = $customers->whereHas('students', function($q) {
$q->where('STATUS', '=', 'FULL');
});
//Filter results
if(Input::get('academic_year') == 'ordered'){
$customers = $customers->orderBy('DFKEY','ASC')->get();
$filtered = $customers->filter(function($customer)
{
if ($customer->orders()->where('academic_year_id','=',$this->current_academic_year->id)->first()) {
return true;
}
});
$customers = $filtered;
return View::make('admin.customers.index',compact('customers'));
}
$customers = $customers->orderBy('DFKEY','ASC')->get();
return View::make('admin.customers.index',compact('customers'));
}

This is actually a very good question!
Simple queries directly on that model won't be a problem because Laravel uses the default connection from the model, but if you have a related model in another database and you execute some advanced queries, like: whereHas('relationName'), Laravel will use the connection of parent which will cause the SQL error about non existing column (because it looks up in wrong database).
This is the best solution for this problem:
In your related model make a constructor like this:
public function __construct(array $attributes = [])
{
$this->table = 'db_name.'.$this->table;
parent::__construct();
}

Unfurtanelly there is no direct way, but you can do it in 2 steps, which is kind of the same way that Laravel does it normally.
Its simple, clean, and efective, also you dont mess with core Laravel functions that may change on an update
$orders = Order::where('academic_year_id', $this->current_academic_year->id)
->select('id')
->pluck('id');
$customers = $customers->whereIn('order_id', $orders)
->orderBy('DFKEY','ASC')
->get();
Hope this works for you.
P.D.: you can skip the equal sign in where

Package hoyvoy/laravel-cross-database-subqueries allows you to do that for databases in the same server. You also need to specify all database connections and models related to them as specified by Agus Trombotto. Finally the model refering to another database must extend from Hoyvoy\CrossDatabase\Eloquent\Model instead of Illuminate\Database\Eloquent\Model.

Try to write query like this and put your database and tablename as it is->
$customers = Schema::connection('your_database_name')::table('respective_table_name')
->where('academic_year_id', '=', $this->current_academic_year->id)
->orderBy('DFKEY','ASC')
->get();

I 've solved this by adding 'connection' and 'table' variables models specificing the database in wich the model is saved.
For example, i have a model called 'User' in the database called 'core_database' in the table users.
In the other side, i have a model called 'UserEvents' in the database called 'logs_database' in the table 'user_events'
So i will have a two conections on config/database.php file:
'core_db_connection' => [
'driver' => 'mysql',
'host' => host_ip,
'port' => port,
'database' => 'core_database',
'username' => username,
'password' => password,
....
],
'logs_db_connection' => [
'driver' => 'mysql',
'host' => host_ip,
'port' => port,
'database' => 'logs_database',
'username' => username,
'password' => password,
....
],
And the models will be like:
class User extends Authenticatable {
protected $table = 'core_database.users';
protected $connection = 'core_db_connection';
...
}
class UserEvents extends Model {
protected $table = 'logs_database.user_events';
protected $connection = 'logs_db_connection';
...
}
This was tested in databases in the same database server.
Database connections have the same host ip.
I have not tried with a different way
Using this configuration, i can make any query across two or more separeted database in the same database server, in my case, RDS.
I hope this help you!

Related

Laravel 6 : Duplicate database entry on polymorphisme relation save

I try to save a morph relationship in my database, but when i try to save it I Have tow entry in every table use for the relation.
here is my client class
class Client extends Model
{
protected $guarded = [];
public function clientelle(){
return $this->morphTo();
}
}
my particulier class
class Particulier extends Model
{
protected $guarded = [];
public function client(){
return $this->morphOne(Client::class,'clientelle');
}
}
So when I try to save like that :
$particulier = new Particulier();
$particulier->nom = $request->nom;
$particulier->prenom = $request->prenom;
$particulier->save();
$particulier->client()->create(['telephone'=>$request->telephone,'adresse'=>$request->adresse,'email'=>$request->email]);
My database save two same recorde. Here is my problem.
So I have try diffrente thing to avoid it but I have error every time
delete $particulier->save(); but SQL error id don't exist
replace create([...]) by save([...]) or sync([...]) but don't work
Thank you in advance
So I have finally find a worst solution...
In your controller add int var and init it to 0 like that :
Private $checkDouble=0;
In place where you want save your relation create if block and put in all of your model save like that :
if($this->checkDouble==0) {
$this->checkDouble = $this->checkDouble + 1; //Increase your var value
$particulier = Particulier::create(['nom' => $request->nom, 'prenom' => $request->prenom]);
$particulier->client()->create(['telephone' => $request->telephone, 'adresse' => $request->adresse, 'email' => $request->email]);
}
And no more duplicate data
I know is really worst solution but I don't see any best solution ^^

Better Code for Authentication in Laravel

I am really new to PHP and Laravel and i am a little embarrassed for asking this Questions but none of the things i am trying to reuse code in my Controllers work.
I have this function in my PagesController for setting up the welcome-view of my Laravel Web Application:
public function welcome(Request $request)
{
$cities = City::all();
$user_id = $request->session()->pull('user_id');
$user = User::find($user_id);
if($user !== null )$request->session()->put('user_id', $user->id);
return view('welcome',[
'cities' => $cities,
'user_id' => $user_id,
'user' => $user
]);
}
the important stuff are the three lines which gets the user_id and finds the fitting User from the Database.
I'd like to define a function in my BaseController and use it in every other Controller as well.
What would be the easiest way to do this?

User doesn't update Laravel

Update Method:
public function update(UserUpdateRequest $request, Users $uzytkownik)
{
$this->authorize('update', $uzytkownik);
if ( $uzytkownik->update([
'birth' => $request->birth,
'sex' => $request->sex,
'about' => $request->about,
]) )
{
return 1;
}
return 0;
}
On update here in page 1 appears. Like it did the thing.
But in db nothing has changed.
$uzytkownik is proper user, and
This is the dd($uzytkownik);
And below dd($request->birth.'---'.$request->sex.'---'.$request->about); which shows proper inputs
Why it doesn't work properly?
As per the documentation
Mass Assignment
You may also use the create method to save a new model in a single line. The inserted model instance will be returned to you from the method. However, before doing so, you will need to specify either a fillable or guarded attribute on the model, as all Eloquent models protect against mass-assignment by default.
You need to make sure $fillable or $guarded is correctly set otherwise changes may not be persistant.
You can do what you want like this too:
public function update(UserUpdateRequest $request, Users $uzytkownik)
{
$this->authorize('update', $uzytkownik);
$uzytkownik->birth = $request->birth;
$uzytkownik->sex = $request->sex;
$uzytkownik->about = $request->about;
if ( $uzytkownik->save() )
{
return 1;
}
return 0;
}

Laravel - Seeding Relationships

In Laravel, database seeding is generally accomplished through Model factories. So you define a blueprint for your Model using Faker data, and say how many instances you need:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
$user = factory(App\User::class, 50)->create();
However, lets say your User model has a hasMany relationship with many other Models, like a Post model for example:
Post:
id
name
body
user_id
So in this situation, you want to seed your Posts table with actual users that were seeded in your Users table. This doesn't seem to be explicitly discussed, but I did find the following in the Laravel docs:
$users = factory(App\User::class, 3)
->create()
->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
So in your User factory, you create X number of Posts for each User you create. However, in a large application where maybe 50 - 75 Models share relationships with the User Model, your User Seeder would essentially end up seeding the entire database with all it's relationships.
My question is: Is this the best way to handle this? The only other thing I can think of is to Seed the Users first (without seeding any relations), and then pull random Users from the DB as needed while you are seeding other Models. However, in cases where they need to be unique, you'd have to keep track of which Users had been used. Also, it seems this would add a lot of extra query-bulk to the seeding process.
You can use saveMany as well. For example:
factory(User::class, 10)->create()->each(function ($user) {
$user->posts()->saveMany(factory(Posts::class, 5)->make());
});
You can do this using closures within the ModelFactory as discussed here.
This solution works cleanly and elegantly with seeders as well.
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'body' => $faker->paragraph(1),
'user_id' => function() {
return factory(App\User::class)->create()->id;
},
];
});
For your seeder, use something simple like this:
//create 10 users
factory(User::class, 10)->create()->each(function ($user) {
//create 5 posts for each user
factory(Post::class, 5)->create(['user_id'=>$user->id]);
});
NOTE: This method does not create unneeded entries in the database, instead the passed attributes are assigned BEFORE the creation of associated records.
Personally I think one Seeder class to manage these relations is nicer then separated seeder classes, because you have all the logic in one place, so in one look you can see what is going on. (Anyone that knows a better approach: please share) :)
A solution might be: one DatabaseSeeder and private methods within the class to keep the 'run' method a bit cleaner. I have this example below, which has a User, Link, LinkUser (many-to-many) and a Note (many-to-one).
For the many-to-many relations I first create all the Links, and get the inserted ids. (since the ids are auto-inc I think the ids could be fetched easier (get max), but doesn't matter in this example). Then create the users, and attach some random links to each user (many-to-many). It also creates random notes for each user (many-to-one example). It uses the 'factory' methods.
If you replace the 'Link' for your 'Post' this should work. (You can remove the 'Note' section then...)
(There is also a method to make sure you have 1 valid user with your own login credentials.)
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
// Create random links
factory(App\Link::class, 100)->create();
// Fetch the link ids
$link_ids = App\Link::all('id')->pluck('id')->toArray();
// Create random users
factory(App\User::class, 50)->create()->each(function ($user) use ($link_ids) {
// Example: Many-to-many relations
$this->attachRandomLinksToUser($user->id, $link_ids);
// Example: Many-to-one relations
$this->createNotesForUserId( $user->id );
});
// Make sure you have a user to login with (your own email, name and password)
$this->updateCredentialsForTestLogin('john#doe.com', 'John Doe', 'my-password');
}
/**
* #param $user_id
* #param $link_ids
* #return void
*/
private function attachRandomLinksToUser($user_id, $link_ids)
{
$amount = random_int( 0, count($link_ids) ); // The amount of links for this user
echo "Attach " . $amount . " link(s) to user " . $user_id . "\n";
if($amount > 0) {
$keys = (array)array_rand($link_ids, $amount); // Random links
foreach($keys as $key) {
DB::table('link_user')->insert([
'link_id' => $link_ids[$key],
'user_id' => $user_id,
]);
}
}
}
/**
* #param $user_id
* #return void
*/
private function createNotesForUserId($user_id)
{
$amount = random_int(10, 50);
factory(App\Note::class, $amount)->create([
'user_id' => $user_id
]);
}
/**
* #param $email
* #param $name
* #param $password
* #return void
*/
private function updateCredentialsForTestLogin($email, $name, $password)
{
$user = App\User::where('email', $email)->first();
if(!$user) {
$user = App\User::find(1);
}
$user->name = $name;
$user->email = $email;
$user->password = bcrypt($password); // Or whatever you use for password encryption
$user->save();
}
}
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'body' => $faker->paragraph(1),
'user_id' => factory(App\User::class)->create()->id,
];
});
So now if you do this factory(App\Post::class, 4)->create() it will create 4 different posts and in the process also create 4 different users.
If you want the same user for all the posts what I usually do is:
$user = factory(App\User::class)->create();
$posts = factory(App\Posts::class, 40)->create(['user_id' => $user->id]);
I want to share the approach i've taken for insert many posts to many users:`
factory(App\User::class, 50)->create()
->each(
function ($u) {
factory(App\Post::class, 10)->create()
->each(
function($p) use (&$u) {
$u->posts()->save($p)->make();
}
);
}
);
`
This workaround worked for me after being all day long looking for a way to seed the relationship
this worked for me in laravel v8
for ($i=0; $i<=2; $i++) {
$user = \App\Models\User::factory(1)->create()->first();
$product = \App\Models\Product::factory(1)->create(['user_id' => $user->id])->first();
}
I use a custom made relateOrCreate function that finds a random entry of that model in the database. If none exist, it creates a new one:
function relateOrCreate($class) {
$instances = $class::all();
$instance;
if (count($instances) > 0) {
$randomIndex = rand(0, (count($instances) - 1));
$instance = $instances[$randomIndex];
}
else {
$instance = $class::factory()->create();
}
return $instance;
}
Then I use it like so:
$relatedUser = relateOrCreate(User::class);
return [
'user_id' => $relatedUser->id,
// ...
];

Table for model was not found in datasource

I'm converting a database from being managed by SQL dumps to being managed by schemas and migrations. Part of this is seeding data. I've based what I'm doing from the schema example on CakePhp's page about Schemas.
The weird thing is that the first table to be seeded with data works without problem, and the second fails with an error like Table users for model User was not found in datasource default. This happens even if I change which table will be seeded: the first one succeeds (and I've checked in the database that the data is there) and the next one to be seeded fails.
I've also checked the error message against the database, and every table it complains about not existing does actually exist.
My 'schema.php' looks like this:
class AppSchema extends CakeSchema {
public function before($event = array()) {
return true;
}
private function create_many($class_name, $entries){
App::uses('ClassRegistry', 'Utility');
$class = ClassRegistry::init($class_name);
foreach($entries as $entry){
$class->create();
$class->save(array($class_name => $entry));
}
}
private function create_many_kv($class_name, $keys, $values_matrix){
$entries = array();
foreach($values_matrix as $values){
array_push($entries, array_combine($keys, $values));
}
$this->create_many($class_name, $entries);
}
public function after($event = array()) {
if (isset($event['create'])) {
switch ($event['create']) {
case 'users':
$this->create_many('User', array(
array('emailaddress' => 'email',
'password' => 'hash',
'role_id' => 1
),
array('emailaddress' => 'email2',
'password' => 'hash',
'role_id' => 3)
));
break;
case 'other_table':
$this->create_many('OtherTable', array(
array('id' => 1,
'name' => 'datum'),
array('id' => 2,
'name' => 'datum2')
));
break;
etc.
The answer for me here was to populate all of the tables after the last table has been created.
My best hypothesis for why this didn't work as described in the question is that Cake is caching the database structure in memory, and this isn't updated when the new tables are added. I can't find any documentation about clearing that structure cache so a workaround is the closest thing to a solution for now.
When inserting data to more than one table you’ll need to flush the database cache after each table is created. Cache can be disabled by setting $db->cacheSources = false in the before action().
public $connection = 'default';
public function before($event = array()) {
$db = ConnectionManager::getDataSource($this->connection);
$db->cacheSources = false;
return true;
}

Categories