Is the database class in Laravel use the "Singleton Pattern"? - laravel

I am new to Laravel. Does the Laravel create a database connection every time for each query of the program or use the same database object throughout the program using the "Singleton" Pattern? Does the strategy have an impact on performance, especially for Enterprise Level Applications?

Short answer
No, it's not a singleton, but a factory pattern. However the same connection will be reused if possible and you don't manually request it to reconnect. There is no performance hit.
Long answer
At the beginning of ever request lifecycle, in app/bootstrap/start.php an instance of Illuminate\Foundation\Application gets created. This servers as IoC container.
Shortly after creating the application all service providers will be loaded. The service providers are defined in app/config/app.php
'providers' => array(
// ...
'Illuminate\Database\DatabaseServiceProvider',
// ...
),
Let's have a look at the Illuminate\Database\DatabaseServiceProvider shall we? The important part is the register function
$this->app->bindShared('db', function($app)
{
return new DatabaseManager($app, $app['db.factory']);
});
An instance of DatabaseManager gets bound to db. This instance will stay the same over the whole request and will be used for every database request.
Example from "reverse" direction
Say you call
DB::table('users')->get();
First, the DB facade will resolve to the instance of DatabaseManager that gets bound in the DatabaseServiceProvider using bindShared('db')
protected static function getFacadeAccessor() { return 'db'; }
Then the table('users') call gets forwarded because the method doesn't exist in Database Manager
public function __call($method, $parameters)
{
return call_user_func_array(array($this->connection(), $method), $parameters);
}
It is called on the return value of $this->connection()
public function connection($name = null)
{
list($name, $type) = $this->parseConnectionName($name);
// If we haven't created this connection, we'll create it based on the config
// provided in the application. Once we've created the connections we will
// set the "fetch mode" for PDO which determines the query return types.
if ( ! isset($this->connections[$name]))
{
$connection = $this->makeConnection($name);
$this->setPdoForType($connection, $type);
$this->connections[$name] = $this->prepare($connection);
}
return $this->connections[$name];
}
With if (!isset($this->connections[$name])) it will check if the connection already has been established and will only make a new connection if not.
Then it returns the connection and table('users')->get() will be executed.

Related

How to include Laravel model boot "deleting" method into DB transaction with the main model delete?

I have a model Agent and it has many agent accounts.
public function agentAccounts(): Relation
{
return $this->hasMany(AgentAccount::class);
}
I want to delete them in one transaction but using boot method
public static function boot()
{
parent::boot();
self::deleting([self::class, 'onDeleting']);
}
I understand, that when I create a Db transaction inside "onDeleting" funcion like this
public static function onDeleting(self $model): void
{
DB::transaction(function () use ($model) {
$agentAccounts = $model->agentAccounts;
foreach ($agentAccounts as $agentAccount) {
/* #var $agentAccount AgentAccount */
$agentAccount->delete();
}
}, 5);
}
The db transaction does not include the deletion of the agent itself.
It precedes the agent deletion db transaction.
In my case agent deletion can fail due to some SQL level restrictions not related to agentAccounts
and If I use the exampel above I can end up with all agentAccounts deleted but the Agent - preserved.
I don't want that to happen.
I want them either get deleted together, or be preserved together.
How can I do it?
I believe DB events are run in a blocking mode, so it's enough when calling the delete() on a model, to do it inside of a transaction, like that
DB::transaction(function () use($user) { $user->delete(); });

Laravel Nova Observe not connecting to tenant database

I have a multi tenant App. My system database I have models- User, Billing, FrontEnd ... and using policies I'm able to show, hide and prevent viewing and actions by tenant.
Each tenant has a database with models- Member, Event, Item ...
I set each model database based on the Auth::user()->dbname in the _construct method. This allows me to set my dbname to a clients database for tech support.
class Item extendsw Model {
public function __construct() {
parent::__construct();
if(Auth::user()->dbname) {
Config::set('database.connections.tenant.database', auth()->user()->dbname);
$this->connection = 'tenant';
}
}
This all works as planned until I add and Observer for a client model e.g. Member
I now get an error on any Observer call.
Trying to get property on non object Auth::user()->dbname.
Where should I be registering the Observer? I tried AppServiceProvider and NovaServiceProvider.
I think that happens because the observer instantiates your User model before the request cycle has started and that means that your User instance does not exist yet neither has been bound in the Auth facade.
Thus, Auth::user() returns null and you are trying to get a property from it.
A way to solve the issue may be to check if the user instance exists or not:
public function __construct() {
parent::__construct();
if (optional(Auth::user())->dbname !== null) {
Config::set('database.connections.tenant.database', auth()->user()->dbname);
$this->connection = 'tenant';
}
}
The optional helper return the value of the accessed property (dbname in your case) if and only if the argument is not null, otherwise the whole call will return a null value instead throwing an exception.
If that is not the case, maybe update the question with the error stacktrack and the code/action that triggers the error

Laravel 5.7: prevent auto load injection in controller constructor

I have a HomeController with his constructor that takes a Guzzle instance.
/**
* Create a new controller instance.
*
* #param \GuzzleHttp\Client|null $client
*
* #return void
*/
public function __construct(Client $client = null)
{
$this->middleware('auth');
$this->middleware('user.settings');
if ($client === null) {
$param = [
'base_uri' => 'http://httpbin.org/',
'defaults' => [
'exceptions' => false,
'verify' => false
]
];
$client = new Client($param);
}
$this->setClient($client);
}
I would use via __constructor() to be able to mock it in tests.
My issues is that Laravel automatically auto-load the injection and the Guzzle Client injected has blank defaults (and cannot anymore edit it). In other words: at first call of HomeController Client is not null. And I need as null.
How can I stop this behaviour (only for the __construct() for HomeController)? I really use the DI in every part of my webapp.
EDIT
I just find that if I don't type-hints the Client, of course Laravel cannot auto-load. Is this the right mode to work?
New constructor:
public function __construct($client = null)
Thank you
I had a simular situation when testing apis. I ended up binding an instance of GuzzleClient to the service container (see documentation). Something like:
$this->app->instance('GuzzleHttp\Client', new MockClient);
To successfully mock the instance, I then checked to see whether or not it had a certain property value (in my case base_url being set). That determined whether or not the instance was a test as base_url would be set.
Along side this method, GuzzleHttp\Client does have a MockHandler you may want to explore. This can be used to fake response bodies, headers and status codes.

Saving an object into the session or cookie

I'm using Instagram API library to connect user to Instagram profile and then do smth with it. So, as Instagram API wiki says:
Once you have initialized the InstagramAPI class, you must login to an account.
$ig = new \InstagramAPI\Instagram();
$ig->login($username, $password); // Will resume if a previous session exists.
I have initialized InstagramAPI class and then I called $ig->login('username', 'password');. But I have to call it in every function where I need to work with Instagram.
So how could I save this object $ig for using it in the future in other controllers without calling login() any more? Can I save $ig object into the session or cookie file?
P.S. I think saving into the session is not safe way to solve the issue.
UPD: I was trying to save $ig object into the session, however the size is large and session become stop working as well.
Regarding the register method you asked in the comments section, all you need to create a new service provider class in your app\providers directory and declare the register method in there for example:
namespace App\Providers;
use InstagramAPI\Instagram;
use Illuminate\Support\ServiceProvider;
class InstagramServiceProvider extends ServiceProvider
{
public function register()
{
// Use singleton because, always you need the same instance
$this->app->singleton(Instagram::class, function ($app) {
return new Instagram();
});
}
}
Then, add your newly created InstagramServiceProvider class in providers array inside the config/app.php file for example:
'providers' => [
// Other ...
App\Providers\InstagramServiceProvider::class,
]
Now on, in any controller class, whenever you need the Instagram instance, all you need to call the App::make('InstagramAPI\Instagram') or simply call the global function app('InstagramAPI\Instagram') or even you can typehint the class in any method/constructor etc. Some examples:
$ig = App::make('InstagramAPI\Instagram');
$ig = App::make(Instagram::class); // if has use statement at the top fo the class
$ig = app('...');
In a class method as a dependency:
public function someMethod(Instagram $ig)
{
// You can use $ig here
}
Hope this helps but read the documentation properly, there will get everything documented.

Laravel inject sentry user into model

I keen to make my code decouple and ready for testing.
I have an Eloquent model getBudgetConvertedAttribute is depend on sentry user attribute.
public function getBudgetConvertedAttribute()
{
return Sentry::getUser()->currency * $this->budget;
}
This throw error while testing because Sentry::getUser is return null.
My question is, How shall I code to inject user into model from controller or service provider binding or testing?
Inject a $sentry object as a dependency in the constructor instead of using the Sentry Facade.
Example
use Path\To\Sentry;
class ClassName
{
protected $sentry
public function __construct(Sentry $sentry)
{
$this->sentry = $sentry;
}
public function methodName()
{
$this->sentry->sentryMethod();
}
}
Why not just create a method on the model, then takes a Sentry user object as a parameter?
public function getBudgetConverted(SentryUser $user)
{
return $user->currency * $this->budget;
}
You’ll need to change the type-hint (SentryUser) to the actual name of your user class.
If this is to aid testing, you could go one step furhter and type-hint on an interface (which you should be any way), that way you could test your method with a mock user object rather than one that may have a load of other dependencies like a database connection, which Eloquent models do.

Resources