I m trying to get Model relationship with different connection, basically my connections are dynamic.
$con = 'con1'
$persons = new \App\Models\Person;
$persons->setConnection($con);
$persons = $persons->where('somevalue', 1)->get()
So here I get Person from con1 (where con1 is stored in config/databse.php
It can be con2, con3, con4 and etc.
However this works, but when I am trying to loop through this data and get relationship it switches to default Database connection.
#foreach($persons as $person)
{{$person->data->name}}
#endforeach
In above loop data is a belongsTo relation ship in Person Model, and it throws error because it switches back to default Database connection and not using con1
It is possible to setConnection() and keep for hasMany relationship and also for belongsTo relationship?
I cant set protected $connection; in Model because connections are changble
What I tried until now is I created an absctract class that extendseloquent, and my models extending this abstract class
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as Eloquent;
abstract class Database extends Eloquent {
protected $connection;
}
<?php
namespace App\Models;
use App\Models\Database as Database;
class Person extends Database{
protected $table = 'persons';
}
So theoritically when I setConnection I set connection to all models that extends this class but still no luck.
You may try eager loading of the relationship:
$persons = $persons->with('data')->where('somevalue', 1)->get();
I found a solution maybe wrong but is working:
I create a file in config where I add all my connections:
<?php
return [
'con1' => [
'name' => 'Persons Connection',
'url' => 'persons',
'active' = true
],
'con2' => [
'name' => 'Italy Persons Connection',
'url' => 'italypersons',
'active' = false
],
];
So like this I can event control if con2 is available
In Database connection config file I have con1 connection with all db data
After in routes.php a bind the url example /info/{con} where con = 'persons'
Route::bind('con', function($con, $route){
foreach(config('connections') as $key => $value){
if($value['url'] == $con && $value['active'] == true){
session(['connection' => $key]); //where $key == con1
return $key;
}
abort(404);
}
});
So: I have a Class Person that extends abstract method with __construct function
<?php
namespace App\Models;
use App\Models\Database as Database;
class Person extends Eloquent{
protected $table = 'persons';
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as Eloquent;
abstract class Database extends Eloquent {
function __construct(array $attributes = array())
{
parent::__construct($attributes);
$this->setConnection(session('connection'));
}
}
So like this I am sure if user get to URL /info/{con} It will set the connection sessions if it exist in my connections config file, without losing sensitive data from config/database.php
If somebody have better idea please write it
Related
I have an application using Vue + Laravel and I am using mysql database. Now, I need to use mongodb database too.
So, here is my live mongodb database table (projects) and collection (product_1, product_2 etc...)
Like this:
https://prnt.sc/D08akhBur6z4
Now, I want to get all the collection. To do that I have created Model called Import
Import.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Jenssegers\Mongodb\Eloquent\Model;
class Import extends Model
{
protected $connection = 'mongodb';
protected $collection = 'projects';
}
and created a controller called ImportController.php.
ImportController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Models\Import;
class ImportController extends Controller
{
public function show_import () {
$all_import = Import::all();
return response()->json( $all_import, 200);
}
}
.env file
MONGO_DB_HOST=107.200.220.71
MONGO_DB_PORT=57019
MONGO_DB_DATABASE=projects
MONGO_DB_USERNAME=marketplus_pr
MONGO_DB_PASSWORD="my-password"
database.php
'mongodb' => [
'driver' => 'mongodb',
'dsn' => 'mongodb+srv://marketplus_pr:my-password#107.200.220.71/projects?retryWrites=true&w=majority',
'database' => 'projects'
],
Now using this api route call:
http://localhost:3000/api/projects/import/show-import/343-3-3-3-3
I am getting this message:
{"success":false}
But Its should give me all the collection, right?
Can you tell me what I am doing wrong here?
Your request is being intercepted by a different route declaration / controller. (chat reference) Also, wrong collection name.
You should make a few changes in the api.php file:
Move this route declaration:
Route::get('projects/import/show-import/{token}', [App\Http\Controllers\ImportController::class, 'show_import'])->name('show_import');
just after:
Route::group([], function ($router) {
making it as:
Route::group([], function ($router) {
Route::get('projects/import/show-import/{token}',
[App\Http\Controllers\ImportController::class, 'show_import'])-
>name('show_import');
...
}
also, the {token} URL parameter makes not sense so you should remove it.
and, change the collection name to products_1 in the Import.php model file:
class Import extends Model
{
protected $connection = 'mongodb';
protected $collection = 'products_1'; // this should be collection name and not the database name: products_1, products_2, etc.
...
}
By default mongodb runs on port 27017, but looking at your connection string you are not sending that port to the connection. In case you are sure you are running mongodb in that port your connection string needs to be:
mongodb+srv://marketplus_pr:my-password#107.200.220.71:57019?retryWrites=true&w=majority
On the other hand if you are not set the port at the moment of running your mongodb instance, this would be 27017, so the connection string will be:
mongodb+srv://marketplus_pr:my-password#107.200.220.71:27017?retryWrites=true&w=majority
controller code : code for controller works for Employers pagination but unable to work pagination about Stories controller.
public $paginate = [
'Employers' => ['scope' => 'employer'],
'Stories' => ['scope' => 'story']
];
public function index()
{
// Paginate property
$this->loadComponent('Paginator');
// In a controller action
$stories = $this->paginate($this->Stories, ['scope' => 'story']);
$employers = $this->paginate($this->Employers, ['scope' => 'employer']);
pr($stories);
$this->set(compact('employers', 'stories'));
}
Model code: model description stand same for all model as yet but understand that model definition unable to work for stories model but as we progress with model definition about employers table that works absolutely fine.
<?php
// src/Model/Table/EmployersTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
class EmployersTable extends Table
{
public function initialize(array $config): void
{
$this->addBehavior('Timestamp');
}
}
<?php
// src/Model/Entity/Employer.php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Spk extends Entity
{
protected $_accessible = [
'*' => true,
'id' => false,
'slug' => false,
];
}
<?php
// src/Model/Table/StoriesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
class StoriesTable extends Table
{
public function initialize(array $config): void
{
$this->addBehavior('Timestamp');
}
}
<?php
// src/Model/Entity/Story.php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Sty extends Entity
{
protected $_accessible = [
'*' => true,
'id' => false,
'slug' => false,
];
}
Bug i keep looking at as i get through load page action i face that Employers data called but Stories data unable to load. Suggestions are open to view look forward to your answers.
error message:
Undefined property: EmployersController::$Stories in /Applications/MAMP/htdocs/sd/sd/src/Controller/EmployersController.php
Surely it's possible, the feature is explicitly documented. The error has nothing to do with pagination, it simply means that the property that you're trying access ($this->Stories) doesn't exist.
Controllers only have one default model that is being loaded automatically, and that's the model that matches the controller name according to the conventions, so in your EmployersController that's the Employers model. Additional models need to be loaded manually:
$this->loadModel('Stories');
// ...
$stories = $this->paginate($this->Stories, ['scope' => 'story']);
See also
Cookbook > Controllers > Loading Additional Models
No, That is not possible as CakePHP only works for single table with multiple pagination query request to same model. But doesn't not apply to many models.
I have got a dev database and a live database. I need to return some results from the live database but only for one method within this model.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class TableName extends Model
{
protected $table = 'table_name';
protected $connection = 'dev';
public $timestamps = false;
public static function live($index) {
$liveId = Settings::where('index', $index)->get()[0];
$live = new TableName;
$live->setConnection('live');
$data = $live::where('index', $liveId->live_index)->get();
dd($live);
return $data;
}
}
If I dd() the $live variable after calling setConnection then it does say that the connection is indeed live. However as soon as I dd() the $data I get the rows from the dev database!
Eloquent provides a nice way to handle multiple connections.
You should just be able to use the on method. For example.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class TableName extends Model
{
protected $table = 'table_name';
protected $connection = 'dev';
public $timestamps = false;
public static function live($index) {
$liveId = Settings::where('index', $index)->get()[0];
$data = self::on('live')->where('index', $liveId->live_index)->get();
return $data;
}
}
That should then run the query using the live connection in your database configuration.
I have personally haven't done anything like this, but I found out way to do this by following these steps.
In the .env file add these new env variables =>
DB_CONNECTION_2=mysql
DB_HOST_2=127.0.0.1
DB_PORT_2=3306
DB_DATABASE_2=database2
DB_USERNAME_2=root
DB_PASSWORD_2=secret
Now inside the config/database.php file specify the 2nd mysql connection with the previously entered env variables.
'mysql2' => [
'driver' => env('DB_CONNECTION_2'),
'host' => env('DB_HOST_2'),
'port' => env('DB_PORT_2'),
'database' => env('DB_DATABASE_2'),
'username' => env('DB_USERNAME_2'),
'password' => env('DB_PASSWORD_2'),
],
Now you can create a Model for the required table =>
class myModel extends Eloquent {
protected $connection = 'mysql2';
}
Then you can use it as the regular way will all the Eloquent features in controller methods =>
$newMy = new myModel;
$newMy->setConnection('mysql2');
$newMy = $someModel->find(1);
return $something;
Here is the doc link that you can read about this more.
You can try to get the default connection before the point with
$defaultConnection = DB::getDefaultConnection();
then change the default connection to before obtaining the results from 'live'
DB::setDefaultConnection('live');
and then restore the connection as soon as 'live' connection is no longer needed
DB::setDefaultConnection($defaultConnection);
As an alternative you can generate your data using DB::connection('live'). More info at Using Multiple Database Connections
First of all, I'm new to Laravel. I come from Codeigniter where you can have something similar to:
class Test_Model extends CI_Model {
public function test_method($a, $b){
return $a * $b;
}
}
class Test_Controller extends CI_Controller {
public function __construct(){
$this->load->model('Test');
}
public function method1() {
$z = $this->Test->test_method(3,4);
}
}
As you can see, the model was loaded and all it's methods were available. In my opinion it's pretty straightforward.
Now, I've got the following in Laravel:
namespace App;
use Illuminate\Database\Eloquent\Model;
// Order Model
class Order extends Model
{
protected $fillable = ['user_id'];
public function orderItems()
{
return $this->hasMany(orderItem::class);
}
}
// orderItem model
namespace App;
use Illuminate\Database\Eloquent\Model;
class orderItem extends Model
{
protected $fillable = [
'order_id',
'item_id',
'type',
'quantity',
'price',
'subtotal'
];
public function order()
{
return $this->belongsTo(Order::class);
}
}
// Orders Controller
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Order;
use App\orderItem;
class OrdersController extends Controller
{
private $orderId;
public function store(Request $request)
{
// check if there's already a cart [order] for this user, if not create one
$this->orderId = Order::where('user_id', $request->json('user_id'))->get(['id']);
$item = [
'item_id' => $request->json('item_id'),
'type' => $request->json('type'),
'quantity' => $request->json('quantity'),
'price' => $request->json('price'),
];
if (!$this->orderId->count()){
$this->orderId = Order::insertGetId([
'user_id' => $request->json('user_id')
]);
}
$orderItem = new Order();
$orderItem->addOrderItem($item, $this->orderId);
}
}
Two questions I've got:
Is there a simpler or cleaner (not saying that creating a new obj is not clean) to access the Order model methods (such as done in Codeigniter)?
I've learnt a little bit about how to establish relationships between models in Laravel. I've got two other tables name Record and Artist respectively (a 1 to many relationship) and I can do the following:
$record = Record::findOrFail($id);
$record['artist_name'] = $record->artist->name;
but when I try to do the same with the Order and orderItem (also a 1 to many relationship) it doesn't work:
$cart = Order::where('user_id', $user->id)->get();
// Retrieve existing items in cart
$cart_items = $cart->orderItems();
Why is that?
As for question 1:
If the method has nothing to do with the actual instance of the model I would strongly recommend not putting it on the model. You could create a separate class that doesn't extend Model class as there is no need to.
If you really want to, you could create a static method though.
If it does depend on the model (database row), there's no way of not instantiating it as it will need to know which database row to work on.
As for question 2:
This part $cart_items = $cart->orderItems(); only returns a query builder (as you're calling it as a function and not a property). Which lets you chain other query builder methods off of it.
For example $cart_items = $cart->orderItems()->get(); will return the actual order items.
Or you could just call it as a property and get the same result:
$cart_items = $cart->orderItems;
While the above should work, it is generally suggested that you eager load the relationships (especially when you're pulling multiple rows of the parent model), which would look like this (the ->with() part will eager load them):
$cart = Order::where('user_id', $user->id)->with('orderItems')->get();
$cart_items = $cart->orderItems;
I have a model named 'Poll'. Inside Poll model I defined a boot method like follows:
public static function boot()
{
parent::boot();
self::created(function($model){
// dd($model);
$speakers = $model->speakers()->get();
// dd($speakers);
// What I want to do here is: create poll options relation from speakers as follows
// $poll->poll_options()->create([
// 'option' => $speaker->name,
// ]);
}
}
I am adding the speakers relation and it is working perfect.
But inside this boot method, inside self::created if I tried to get the speakers relation, it is always empty (dd($speakers) line). Is it because of the boot method runs just after the model is saved into DB and the relations not at all saved?
I am getting newly created model in the line: dd($model) mentioned in the code.
UPDATE
I tried with events also.
My Poll Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
use Cookie;
use App\Events\PollCreated;
class Poll extends Model
{
........
protected $events = [
'created' => PollCreated::class,
];
.......
public function speakers()
{
return $this->belongsToMany('App\Models\Speaker','poll_speaker','poll_id','speaker_id');
}
}
app/Events/PollCreated.php:
namespace App\Events;
use App\Models\Poll;
use Illuminate\Queue\SerializesModels;
class PollCreated
{
use SerializesModels;
public $poll;
/**
* Create a new event instance.
*
* #param Poll $poll
* #return void
*/
public function __construct(Poll $poll)
{
// $this->poll = $poll;
$event = $poll->event()->first();
// dd($event);
// dd($poll->speakers()->get());
// dd($poll->load('speakers'));
}
}
Here also I am not getting speakers, in the line: dd($poll->speakers()->get());
my Speaker model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
class Speaker extends Model
{
use CrudTrait;
……..
public function polls()
{
return $this->belongsToMany('App\Models\Poll');
}
……..
}
The problem is with timing as models must always be created before they can be set in a many-to-many relationship. So there is no possible way that in a many-to-many relationship during the created event the relationship is already set as the created events are always raised before the relationships.
Anyone looking for a solution can probably experiment with the chelout/laravel-relationship-events package as this adds relationship events to models.
To be sure, I tested this out with a simple application of users and computers.
User.php
class User extends Model
{
use HasBelongsToManyEvents;
public static function boot() {
parent::boot();
self::created(function($model){
Log::info('user::created');
});
static::belongsToManyAttaching(function ($relation, $parent, $ids) {
$ids = implode(' & ', $ids);
Log::info("Attaching {$relation} {$ids} to user.");
});
static::belongsToManyAttached(function ($relation, $parent, $ids) {
$ids = implode(' & ', $ids);
Log::info("Computers {$ids} have been attached to user.");
});
}
public function computers() {
return $this->belongsToMany(Computer::class, 'user_computers');
}
}
Computer class is the same in reverse. And for the following code:
$user = User::create();
$user->computers()->attach([
Computer::create()->id,
Computer::create()->id
]);
This was the outcome:
user::created
computer::created
computer::created
Attaching computers 69 & 70 to user.
Computers 69 & 70 have been attached to user.