I am making a multi-tenant multi-database app that has one central database and many sub-databases.
The app creates an instance of an organisation in the central database, and to each organisation it creates a sub-database with different tables.
To achieve this, I have made a class class Setup that
Creates the Organisation
Creates its Database
Configures the connection to that database and connects to it
Runs the proper migrations to it.
All wrapped up in a constructor, so upon caling Setup::create all of this runs properly.
Most of the database configuration and connection are inspiried from this tutorial.
To test whether my logic goes as wanted, I interacted with my application via :
Tinker
Web Browser.
To my suprise, the outcome is different in both cases, and never as wanted as far as connecting to another database is concerned.
Interaction with tinker :
After creating calling my Setup::create and having output telling me everything went okay, I try to check for myself what database am I on right now Using:
DB::connection()->getDatabaseName()
It outputs the sub-database name we have just created for the organisation and connected to, which is logical and going accordingly.
However, I attempt to connect to another database by creating a new configuration for it and then connecting to it with the DB methods I have provided, it does not work, I am still on the sub-database I was on.
Interacting with the browser :
This time, having my Setup::create wrapped up properly in my controller's code, I attempt to test everything again, I also made a line in my layout to output me the current database :
<?php echo DB::connection()->getDatabaseName() ?>
At first, while I am still on the central database, its name appears, however after calling Setup::create, it switches to the sub-database -Which is expected- but then, after one refresh, I am on the central database again -Which is totally Unexpected-
So, what happens here? and how do I get to connect to all of my different databases how I wish when I wish?
Extra:
Testing in tinker, I have went to the point where I have commented out the migration code, and left the creation of the database and also the connection to it.
To my suprise, it does not connect to the database.
so I started thinking that the migration code has something to do with connecting to the database, or maybe tinker has different behaviors I completely ingore.
Important:
I have came across threads where solutions using QueryBuilders were mentioned
Please, do not provide such answers because my aim is to switch databases entirely to the point where I can use eloquent model's events with no problem.
By that I mean, I want to be able to use Model::create after having connected to the database instead of DB::connection()->....
Technical details:
I am using Laravel 5 with mysql-server, on Ubuntu Machine.
I stumbled upon this question and it had my answer.
I made a class called DatabaseConnection:
class DatabaseConnection extends Model
{
static $instances=array();
protected $database;
protected $connection;
public function __construct($options = null)
{
// Set the database
$database = $options['database'];
$this->database = $database;
// Figure out the driver and get the default configuration for the driver
$driver = isset($options['driver']) ? $options['driver'] : Config::get("database.default");
$default = Config::get("database.connections.$driver");
// Loop through our default array and update options if we have non-defaults
foreach($default as $item => $value)
{
$default[$item] = isset($options[$item]) ? $options[$item] : $default[$item];
}
$capsule = new Capsule;
$capsule->addConnection($default);
$capsule->setEventDispatcher(new Dispatcher(new Container));
$capsule->setAsGlobal();
$capsule->bootEloquent();
// Create the connection
$this->connection = $capsule->getConnection();
DatabaseConnection::$instances[] = $capsule;
return $this->connection;
}
}
So, whenever I am in a controller that manipulates tables of a sub-database, I simply go this way:
public function RandomActionInMyController()
{
$db_connection = new DatabaseConnection(['database' => 'name_of_db']);
$someModel = new Model/Model::find()..// Basically anything
return myreturnstuff;
}
Extra Bonus:
The use of the static attribute $instances in my DatabaseConnection
boils down to retrieving my latest database connection for ease uses.
For example, if I ever wanted to retrieve it, it would be wrapped in a function such as
function CurrentOrLatestDbConnection()
{
if( !empty(DatabaseConnection::$instances) )
{
return end(DatabaseConnection::$instances)->getConnection()->getDatabaseName();
}
}
Notes :
If you encounter errors such as Unknown class 'Container' or Capsule or anything of that kind, make sure you check the question link I have provided, and use use statements properly.
Concerning upcoming answers :
It seems to me that this database connection lives within the the brackets of the controller's action, so when I proceed to another action that specifies no connection, it returns to the central database automatically.
Which has got me thinking that there must be a way to set the database connection to the sub-database in a 'global' way to the whole function, such as a middleware or something.
I would love to see an answer, implementing such thing.
Update :
I came up with a neater way to do it.
I assume you are on the same ground as me, wanting to change databases conditionally in accordance with each controller... say, each of your controllers requires a different database, just for the sake of the argument.
What we will be using to solve this is `Middlewares.
First, to explain what we are about to do..
We are going to check for the name of the controller (and even action) and then set the proper database we wish to set.
Go to your command-line , type in:
php artisan make:middleware SetDatabaseConnectionMiddleware
To create a middleware with ready boilerplate.
Or, if you like it the hard way, go to your app_name/app/Http/Middleware and create one manually.
Go to your helper methods file( if you already have one, if not, dude make one!)
function getControllerAndActionName()
{
$action = app('request')->route()->getAction();
$controller = class_basename($action['controller']);
list($controller, $action) = explode('#', $controller);
return ['action' => $action, 'controller' => $controller];
}
This will return to you an array with both the action name and controller name, if you want to return restrictidly just the controller's name, feel free to remove 'action' => $action from the code.
Inside of your middleware, it will look this way :
namespace App\Http\Middleware;
use Closure;
use DatabaseConnection;
class SetProperDatabase
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$database_name = '';
$controllerAndActionName = getControllerAndActionName();
$controller_name = $controllerAndActionName['controller'];
$action_name = $controllerAndActionName['action'];
if($controller_name == 'my_controller_nameController')
{
$database_name = 'your_proper_database_name';
}
else
{
$database_name = 'other_db';
}
$database_connection = new DatabaseConnection(['database' => $database_name']);
return $next($request);
}
}
4.Now, that you have created properly your middleware, let us tell your app where to find it and under what name.
Go to your app_name/app/Http/Kernel.php
In your $routeMiddleware variable, add this line
'set_proper_database' => \App\Http\Middleware\SetProperDatabase::class,
This way we know how to call it.
Finally, setting it up.
Go to your Controller.php (the Abstract class from which all of your controller's inherit)
public function __construct()
{
$this->middleware('set_proper_database');
}
And this should do it for you.
If you have any further questions, please feel free to comment.
// Resources :
1.Controller And Action Name
2.Middleware Documentation
3.Further Middleware Documentation
Notes :
I'd appreciate some edition concerning my styling and code indenting, since it seems I struggled to style my code properly in here but in vain, the indentions I used had no effeft.
Related
I'm writing unit tests for an API using PHPUnit and Laravel. Most functions I'm testing require that the user is authenticated before the function can be ran. The user data is stored in one table, and their permissions are stored inside of another table. I can fake the user object inside of Laravel, but I need to be able to also pull the corresponding permissions from the other table without having to hit the database like the dingo router currently is doing.
Currently running Laravel 5.8 and PHPUnit 8.1.5. I currently have the users object that I generated from a Laravel factory saved to a text file. I am able to pass that to a function called "actingAsApi" (found on Github, code below) and that allows me to authenticate as that user. However, the function is still going out and getting all permissions for that user from the database. I'm trying to mock or fake the permissions object it is pulling somewhere so that it doesn't need to hit the database at all. I also tried using the built in Passport functions for Passport::actingAs, and those did not work either as they were still hitting the DB (and not really working anyways).
actingAsApi (inside of TestCase.php)
protected function actingAsApi($user)
{
// mock service middleware
$auth = Mockery::mock('Dingo\Api\Http\Middleware\Auth[handle]',
[
Mockery::mock('Dingo\Api\Routing\Router'),
Mockery::mock('Dingo\Api\Auth\Auth'),
]);
$auth->shouldReceive('handle')
->andReturnUsing(function ($request, \Closure $next) {
return $next($request);
});
$this->app->instance('Dingo\Api\Http\Middleware\Auth', $auth);
$auth = Mockery::mock('Dingo\Api\Auth\Auth[user]',
[
app('Dingo\Api\Routing\Router'),
app('Illuminate\Container\Container'),
[],
]);
$auth->shouldReceive('user')
->andReturnUsing(function () use ($user) {
return $user;
});
$this->app->instance('Dingo\Api\Auth\Auth', $auth);
return $this;
}
Test inside of my Test file
public function testActAs() {
$user = 'tests/users/user1.txt';
$this->actingAsApi($user);
$request = new Request;
$t = new TestController($request);
$test = $t->index($request);
}
I expect the actingAsApi function to allow me to also pass in the mock permissions data that corresponds to my mock user object data from the file, but instead it is hitting the database to pull from the permissions table.
EDIT:
So i've been playing around with doing mock objects, and i figured out how to mock the original controller here:
$controlMock = Mockery::mock('App\Http\Controllers\Controller', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
$this->app->instance('App\Http\Controllers\Controller', $controlMock);
but now I can't figure out how to get my call from the other controllers to hit the mocked controller and not a real one. Here is my code for hitting an example controller:
$info = $this->app->make('App\API\Controllers\InfoController');
print_r($info->getInfo('12345'));
How can i make the second block of code hit the mocked controller and not standup a real one like it does in its constructor method?
Finally came on an answer, and it is now fixed. Here's how I did it for those wondering:
$request = new Request;
$controlMock = m::mock('App\API\Controllers\InfoController', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
print_r($controlMock->getInfo('12345'));
Basically, I was trying to Mock the original API controller, and then catch all of the calls thrown at it. Instead, I should've been mocking the controller I'm testing, in this case the InfoController. I can then catch the call 'userHasPermission', which should reach out to the Controller, but I am automatically returning true. This eliminates the need for hitting the database to receive permissions and other info. More information on how I solved it using Mockery can be found here: http://docs.mockery.io/en/latest/cookbook/big_parent_class.html. As you can see, this is referred to as a 'Big Parent Class'. Good luck!
So this is my process. I run a command and it creates a new connection. It then sets that connection as default. THIS works perfectly.
It also overrides another connection. However, since we can only have one default instead of creating a new connection and setting it to default it simply copies the old connection / replaces the database info and then overrides that connection.
I then immediately create a model that uses this connection. I even go into it and dump out $this->getConnection() and see that its pulled in my new settings and configs. My issue is that when I do a ->all() I am getting info from the PREVIOUS overridden connection. I have no clue why.
Here is a sample of my code:
public function handle()
{
if (!is_null($this->env = $this->option('env'))) {
(new Dotenv(base_path(), ".env.{$this->env}"))->overload();
}
$this->setClientConnection();
dd(\Override::getModel('User')->changeConnection('webApp')->get()->pluck('login_name', 'users_id'));
}
/**
* Create the database and a temporary connection
*
* #return null
*/
protected function setClientConnection()
{
$clientDb = env('DB_DATABASE');
$userDb = env('DB_APP_DATABASE');
// Create temporary connection
$newConnection = config('database.connections.data');
$newConnection['database'] = $clientDb;
config(['database.connections.' . $clientDb => $newConnection]);
// Set new connection as default
config(['database.default' => $clientDb]);
// Override Webapp database
$newUserConnection = config('database.connections.webApp');
$newUserConnection['database'] = $userDb;
config(['database.connections.webApp' => $newUserConnection]);
}
Just to reiterate, creating a new connection and setting it to default works perfectly. Creating a new connection and using it to override an existing connection SEEMS to work. config('database.connections.webApp') gives the new connection info. (new App\User)->getConnection() returns the proper config and database is set to the new database. looking at the model object, all appears right. Then I go to make a ->all() call and it pulls in the overridden connection and pulls from the old database.
P.S. the changeConnection method i added to test out some things and to look at different protected attributes. Most of my dumping i did from there and it LOOKS like it should be using the new connection...till it actually makes the call...then no go.
When you run get() on an Eloquent Model it runs the query on the Illuminate\Query\Builder object (after going through the Illuminate\Eloquent\Query\Builder wrapper function).
TheIlluminate\Query\Builder object stores a reference of the connection object used for the query.
Since your model is created first and then the connection is changed, the Query Builder object still uses the old connection
To solve this issue you need to create a fresh instance of the model query builder with the new connection
There are multiple ways to do this:
Use the static function on like so: User::on('connection_name')->find($userId)
Use the newFromBuilder method like so: $model->newFromBuilder($model->getAttributes, 'connection_name')
Use the newQuery method to get a fresh Eloquent builder like so: $model->setConnection('connection_name')->newQuery()->find($userId)
Use the fresh method like so: $model->setConnection('connection_name')->fresh().
Do note however that the fresh method fires a new query whereas the other options set the connection without firing a new query.
Your choice of function would be based on
Whether you already have a model instance or whether you want to use static methods and
Whether you only need the builder object (like #3) or you actually need the model instance
We are currently working on an application with a Google Login with Laravel with Socialite. We have a Auth user who gets a permission number ex. 264. We have made a function which returns an array with all binary numbers this permission number is made off.
Because calling this function every single time a page loads may be kinda heavy, we thought of adding this once when the Auth::user() is created. We thought of adding a custom constructor in the Model, but we can't make it work.
function __construct($attributes = array()) {
parent::__construct($attributes);
$this->permissionsArray = PermissionHelper::permissionConverter($this->permissions);
}
But we can't get it to work, $this doesn't have values when calling this function.
TLDR;
Directly after making the Auth user I want to call the permissionConverter function and save the data to the user so we can use it more often. Any suggestions on how to do this?
EDIT: I checked all answers out today, succeeded with one of them, but I assumed Laravel put the authenticated user in the SESSION or something. I found out it doesn't and it gets all the data from the database every request. We couldn't do what we requested for unfortunately. So I just had to refactor the script and make it as efficient as possible (although it became a bit less readable for less experienced programmers).
Thanks for the help :D
Maybe you can use this solution ? https://stackoverflow.com/a/25949698/7065748
Create a on the User Eloquent model a boot method with
class User extends BaseModel {
public static function boot() {
static::creating(function($model) {
$model->permissionsArray = PermissionHelper::permissionConverter($model->permissions);
});
// do the same for update (updating) if necessary
}
}
Can't you just use this method ?
If new user:
$user = new User(); // or User:create(['...']) directly
$user->name = 'toto';
// and all other data
or
$user = Auth::user();
then
$user->permissionsArray = PermissionHelper::permissionConverter($user->permissions);
$user->save();
A really strange issue here. I had a Laravel 5.2 application which work perfectly. Then I update to Laravel 5.3 to use the new broadcasting features and I face a big issue.
When I update the data (with my application forms or directly in my database) the views are note updated properly. I try to clear cache, views and config but nothing change... I need to go to some others pages and the data finish by appear...
I have a Campaign model and a page which list campaigns. When I remove an entry directly in the database, the list doesn't change in front. Also when I use debugging functions like dd results tell me that data haven't changed...
Is there someone else which faced the same problem ?
I've followed the migration guide to update my 5.2 to 5.3, maybe I forgot something...
Here a piece of my .env file :
DB_CONNECTION=mysql
BROADCAST_DRIVER=redis
CACHE_DRIVER=array
SESSION_DRIVER=file
QUEUE_DRIVER=database
Thanks !
Thank you for sharing this questions.
Laravel successfully upgraded to version 5.3 and there are some deprecations and application service provider and also some new feature like passport are added.
Your problem is with view. As per my knowledge, you need to remove arguments from your "boot" method which are written in EventServiceProvider, RouteServiceProvider, AuthServiceProvider which are available on app/provider/remove_the_arguments_from_boot_method_given_file
In Laravel 5.2:
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
But in Laravel 5.3:
public function boot()
{
parent::boot();
}
Kindly refer Laravel 5.3 docs
I hope, this works for you.
Thanks and regards.
Never store full model in session, it can lead to old data displayed in the application !
After a day of search and refactoring I found what was my original problem !
It's a simple session() statement which cause my application to display invalid data.
History
The dashboard display a list of campaigns which are linked to a client. A user can manage multiple clients so I put the current client in session to know which one is currently used.
The mistake here is that I put the entire client model in session so when I read the session and retrieve data, all the relationships are retrieved too.
The client is the central point to access data in my application. I retrieve the campaigns linked to my client and everything is related to it.
Here the vicious function :
/**
* Retrieve the current client instance when the user is connected
* #return App\Client|null
*/
protected function retrieveCurrentClient()
{
$client = null;
if (Gate::allows('manage-clients')) {
if (null === $client = session('currentClient')) {
$client = Client::all()->first();
session(['currentClient' => $client]);
}
} elseif (Auth::guard()->check()) {
$client = Auth::guard()->user()->client;
}
return $client;
}
In fact the problem appeared when I dig around the Gate definition. If I remove them my application starts working again...
Solution
I just change the function to store in session client id instead of the full Model. Then I retrieve fresh data in each page of my application.
/**
* Retrieve the current client instance when the user is connected
* #return App\Client|null
*/
protected function retrieveCurrentClient()
{
$client = null;
if (Gate::allows('manage-clients')) {
if (null === $client_id = session('client_id')) {
$client = Client::all()->first();
session(['client_id' => $client->id]);
} else {
$client = Client::findOrFail($client_id);
}
} elseif (Auth::guard()->check()) {
$client = Auth::guard()->user()->client;
}
return $client;
}
Don't know if it can help someone else to avoid that mistakes but happy to have found an answer !
i did up a minimalistic Command Pattern example in PHP after reading up about it. i have a few questions ...
i'll like to know if what i did is right? or maybe too minimal, thus reducing the point of the command pattern
interface ICommand {
function execute($params);
}
class LoginCommand implements ICommand {
function execute($params) {
echo "Logging in : $params[user] / $params[pass] <br />";
$user = array($params["user"], $params["pass"]);
// faked users data
$users = array(
array("user1", "pass1"),
array("user2", "pass2")
);
if (in_array($user, $users)) {
return true;
} else {
return false;
}
}
}
$loginCommand = new LoginCommand();
// $tries simulate multiple user postbacks with various inputs
$tries = array(
array("user" => "user1", "pass" => "pass1"),
array("user" => "user2", "pass" => "pass1"),
array("user" => "user2", "pass" => "PaSs2")
);
foreach ($tries as $params) {
echo $loginCommand->execute($params) ? " - Login succeeded!" : " - Login FAILED!";
echo " <br />";
}
i am wondering if there is any difference from simply putting this LoginCommand into a simple function say in the Users class?
if LoginCommand is better fit for a class, won't it be better if it were a static class so i can simply call LoginCommand::execute() vs needing to instanciate an object 1st?
The point of the Command Pattern is being able to isolate distinct functionality into an object (the command), so it can be reused across multiple other objects (the commanders). Usually, the Commander also passes a Receiver to the Command, e.g. an object that the command is targeted at. For instance:
$car = new Car;
echo $car->getStatus(); // Dirty as Hell
$carWash = new CarWash;
$carWash->addProgramme('standard',
new CarSimpleWashCommand,
new CarDryCommand,
new CarWaxCommand);
$carWash->wash();
echo $car->getStatus(); // Washed, Dry and Waxed
In the above example, CarWash is the Commander. The Car is the Receiver and the programme are the actual Commands. Of course I could have had a method doStandardWash() in CarWash and made each command a method in CarWash, but that is less extensible. I would have to add a new method and command whenever I wanted to add new programmes. With the command pattern, I can simply pass in new Commands (think Callback) and create new combinations easily:
$carWash->addProgramme('motorwash',
new CarSimpleWashCommand,
new CarMotorWashCommand,
new CarDryCommand,
new CarWaxCommand);
Of course, you could use PHP's closures or functors for this too, but let's stick to OOP for this example. Another thing where the Commands come in handy, is when you have more than one Commander that needs the Command functionality, e.g.
$dude = new Dude;
$dude->assignTask('washMyCarPlease', new CarSimpleWashCommand);
$dude->do('washMyCarPlease', new Car);
If we had hardcoded the washing logic into the CarWash, we would now have to duplicate all code in the Dude. And since a Dude can do many things (because he is human), the list of tasks he can do, will result in a terrible long class.
Often, the Commander itself is also a Command, so you can create a Composite of Commands and stack them into a tree. Commands often provide an Undo method as well.
Now, looking back at your LoginCommand, I'd say it doesn't make much sense to do it this way. You have no Command object (it's the global scope) and your Command has no Receiver. Instead it returns to the Commander (which makes the global scope the Receiver). So your Command does not really operate on the Receiver. It is also unlikely, that you will need the abstraction into an Command, when doing the login is only ever done in one place. In this case, I'd agree the LoginCommand is better placed into an Authentication adapter, maybe with a Strategy pattern:
interface IAuthAdapter { public function authenticate($username, $password); }
class DbAuth implements IAuthAdapter { /* authenticate against database */ }
class MockAuth implements IAuthAdapter { /* for UnitTesting */ }
$service = new AuthService();
$service->setAdapter(new DbAuth);
if( $service->authenticate('JohnDoe', 'thx1183') ) {
echo 'Successfully Logged in';
};
You could do it somewhat more Command-like:
$service = new LoginCommander;
$service->setAdapter(new DbAuth);
$service->authenticate(new User('JohnDoe', 'thx1138'));
if($user->isAuthenticated()) { /* ... */}
You could add the authenticate method to the User of course, but then you would have to set the Database adapter to the User in order to do the authentication, e.g.
$user = new User('JohnDoe', 'thx1138', new DbAuth);
if ( $user->authenticate() ) { /* ... */ }
That would be possible too, but personally, I don't see why a User should have an Authentication adapter. It doesn't sound like something a user should have. A user has the Credentials required by an Authentication adapter, but the not the adapter itself. Passing the adapter to the user's authenticate method would be an option though:
$user = new User('JohnDoe', 'thx1138');
if ( $user->authenticateAgainst($someAuthAdapter) ) { /* ... */ }
Then again, if you are using ActiveRecord, then your user will know about the database anyway and then you could simply dump all the above and write the entire authenticatation code into the user.
As you can see, it boils down to how you are setting up your application. And that brings us to to the most important point: Design Patterns offer solutions to common problems and they let us allow to speak about these without having to define tons of terms first. That's cool, but often, you will have to modify the patterns to make them solve your concrete problem. You can spend hours theorizing about architecture and which patterns to use and you wont have written a single code. Don't think too much about if a pattern is 100% true to the suggested definition. Make sure your problem is solved.