I'm trying to move to another database dynamically. I've seen several questions that showed change db files from one to another and they just getting some information from next database. But what I need is completely moving to second database. How should I do this? I've seen that in order to achieve this dsn (in db.php file) should be altered. But I changed it and it's still not changed?? I should have full access to second database closing first one. Give me advice please
Configs like db.php are not intended to be changed in process (while PHP is processing). They are loaded once in the initialization, when request is entered the framework.
As an alternative, you can configure second DB beforehand in db.php, and change between them dynamically like:
Yii::$app->db // your default Database
and
Yii::$app->db2 // Second configured Database, to which you can switch dynamically later
You can learn about multiple database connections here
So, if you want ActiveRecord(for instance User) to be able to access two databases, you can define some static variable, which specifies from which DB to read/write. For example:
class User extends \yii\db\ActiveRecord
{
const DB_DATABASE1 = 'db1';
const DB_DATABASE2 = 'db2';
private static $db = self::DB_DATABASE1;
public static function setDb($db)
{
self::$db = $db;
}
public static function getDb()
{
switch (self::$db) {
case self::DB_DATABASE1:
return Yii::$app->db;
case self::DB_DATABASE2:
return Yii::$app->db2;
default:
throw new \Exception("Database is not selected");
}
}
//...
And then use it in Controller like:
User::setDb(User::DB_DATABASE1);
$usersDB1 = User::find()->all();
User::setDb(User::DB_DATABASE2);
$usersDB2 = User::find()->all();
Set up multiple database connections in your main.php or main-local.php configuration
Yii::$app->db1;
Yii::$app->db2;
Yii::$app->db3;
when making inquiries
$usersDB1=User::find()->all(Yii::$app->db1);
$usersDB2=User::find()->all(Yii::$app->db2);
$usersDB3=User::find()->all(Yii::$app->db3);
Globally switch to a different database dynamically
I have combined Yerke's answer above and Write & use a custom Component in Yii2.0 here.
First, create a component class to do the DB switching. Here, I call it DbSelector and put it in app\components\DbSelector.php.
namespace app\components;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
class DbSelector extends Component {
const DB_MAIN = 'db';
const DB_SUB1 = 'db1';
const DB_SUB2 = 'db2';
private static $db = self::DB_MAIN;
public static function setDb($db)
{
self::$db = $db;
}
public static function getDb()
{
return \Yii::$app->get(self::$db);
}
}
Second, modify the config/web.php file or whichever your config file is to have multiple databases and add DbSelector as a Yii::$app component.
$config = [
'components' => [
//...
'db' => $db,
'db1' => $db1,
'db2' => $db2,
'dbSelector' => [
'class' => 'app\components\DbSelector',
],
//...
],
//...
];
Third, in each model class, add the following static function getDb() to call the DbSelector getDb():
public static function getDb()
{
return \Yii::$app->dbSelector->getDb();
}
For example,
class DiningTable extends \yii\db\ActiveRecord
{
public static function tableName()
{
return '{{%dining_table}}';
}
public static function getDb()
{
return \Yii::$app->dbSelector->getDb();
}
//...
}
class Customer extends \yii\db\ActiveRecord
{
public static function tableName()
{
return '{{%customer}}';
}
public static function getDb()
{
return \Yii::$app->dbSelector->getDb();
}
//...
}
Lastly, to globally switch to a different database, use \Yii::$app->dbSelector->setDb(YOUR_PREFERRED_DB),
use app\components\DbSelector;
//...
\Yii::$app->dbSelector->setDb(DbSelector::DB_SUB1);
$tables_1 = DiningTable::findAll();
$customers_1 = Customer::find()->where(['<', 'dob', '2000-01-01'])->all();
\Yii::$app->dbSelector->setDb(DbSelector::DB_MAIN);
$tables_main = DiningTable::findAll();
$customers_main = Customer::find()->where(['<', 'dob', '2000-01-01'])->all();
Related
I'm currently going through some tutorial about React Design Patterns, subject: Custom Hooks.
While the concept feels awesome and seems familiar to me, the solution below provided by the tutor made me question how to deal with different data sources.
Is there something like the above mentioned DAO Factory Pattern, you can find in Frameworks like J2E?
Or how is the common approach to handle this challenges in React to make the code maintainable?
My first intension woulda be throwing the stuff into a Factory Component and having implementations for the specific providers, basically like it is shown in the two other Code snippets bewlow.
Is this the regular wayt to do it?
Any help, tips, additional sources to learn this would be highly appreciated.
Here's the solution, with the both possible implementations thrown into the React Form Component:
import axios from 'axios';
import { useDataSource } from './useDataSource';
import { useResource } from './useResource';
import { useUser } from './useUser';
const serverResource = resourceUrl => async () => {
const response = await axios.get(resourceUrl);
return response.data;
};
const localStorageResource = key => () => {
return localStorage.getItem(key);
}
export const UserInfo = ({ userId }) => {
// const user = useResource(`/users/${userId}`);
const user = useDataSource(serverResource(`/users/${userId}`));
const message = useDataSource(localStorageResource('message'));
const { name, age, hairColor, hobbies } = user || {};
return user ? (
<>
<h3>{name}</h3>
<p>Age: {age} years</p>
<p>Hair Color: {hairColor}</p>
<h3>Hobbies:</h3>
<ul>
{hobbies.map(hobby => <li key={hobby}>{hobby}</li>)}
</ul>
</>
) : <p>Loading...</p>;
}
And here's the DAO Factory Pattern example provided by Oracle:
src: https://www.oracle.com/java/technologies/dataaccessobject.html
// Abstract class DAO Factory
public abstract class DAOFactory {
// List of DAO types supported by the factory
public static final int CLOUDSCAPE = 1;
public static final int ORACLE = 2;
public static final int SYBASE = 3;
...
// There will be a method for each DAO that can be
// created. The concrete factories will have to
// implement these methods.
public abstract CustomerDAO getCustomerDAO();
public abstract AccountDAO getAccountDAO();
public abstract OrderDAO getOrderDAO();
...
public static DAOFactory getDAOFactory(
int whichFactory) {
switch (whichFactory) {
case CLOUDSCAPE:
return new CloudscapeDAOFactory();
case ORACLE :
return new OracleDAOFactory();
case SYBASE :
return new SybaseDAOFactory();
...
default :
return null;
}
}
}
...with a concrete implementation:
// Cloudscape concrete DAO Factory implementation
import java.sql.*;
public class CloudscapeDAOFactory extends DAOFactory {
public static final String DRIVER=
"COM.cloudscape.core.RmiJdbcDriver";
public static final String DBURL=
"jdbc:cloudscape:rmi://localhost:1099/CoreJ2EEDB";
// method to create Cloudscape connections
public static Connection createConnection() {
// Use DRIVER and DBURL to create a connection
// Recommend connection pool implementation/usage
}
public CustomerDAO getCustomerDAO() {
// CloudscapeCustomerDAO implements CustomerDAO
return new CloudscapeCustomerDAO();
}
public AccountDAO getAccountDAO() {
// CloudscapeAccountDAO implements AccountDAO
return new CloudscapeAccountDAO();
}
public OrderDAO getOrderDAO() {
// CloudscapeOrderDAO implements OrderDAO
return new CloudscapeOrderDAO();
}
...
}
how is the common approach to handle this challenges in React to make the code maintainable?
sure you can. As design patterns do not depend on programming language.
If you are using TypeScript, then you can use Abstract class
If you are using plain JavaScript, then you can use the following approach
An example of using Factory in React can be seen here.
How do i access another model within a model in cakephp4.2? The docs on this issue isnt clear to me and i can then run a query on this ? TableRegistry is deprecated now.
error Unknown method "getTableLocator" called on App\Model\Table\LessonsTable
//none of these no longer work
in model {
use Cake\ORM\Locator\LocatorAwareTrait;
class LessonsTable extends Table
{
..
private function getlessonRevenue(){
//$clients = $this->getTableLocator()->get('Clients');
// $cleints = TableRegistry::get('Clients');
// $this->Table = TableRegistry::get('Clients');
$clients = $this->getTableLocator()->get('Clients');
https://api.cakephp.org/4.0/class-Cake.ORM.TableRegistry.html
Try:
<?php
use Cake\ORM\Locator\LocatorAwareTrait; //<------------ add here
class ArchivesTable extends Table
{
use LocatorAwareTrait; // <--------------------------- and add here
public function myMethod()
{
$clients = $this->getTableLocator()->get('Clients');
}
and read https://book.cakephp.org/4/en/orm/table-objects.html#using-the-tablelocator
and learn how to use php trait https://www.phptutorial.net/php-tutorial/php-traits/
I have the below code where I am trying to re-encode passwords as users log in (the database has bee migrated form a legacy website). However, I'm not sure what I'm doing wrong as I keep getting errors:
Attempted to call an undefined method named "forward" of class "AppBundle\Service\HubAuthenticator".
I have set things up as follows:
security.yml
security:
encoders:
AppBundle\Entity\Member:
id: club.hub_authenticator
services.yml
services:
//This should be central service than then calls the second
club.hub_authenticator:
class: AppBundle\Service\HubAuthenticator
club.password_rehash:
class: AppBundle\Service\PasswordRehash
Hubauthenticator.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
function __construct($cost=13)
{
parent::__construct($cost);
}
function isPasswordValid($encoded, $raw, $salt)
{
// Test for legacy authentication (and conditionally rehash the password stored in the database if true)
if ($this->comparePasswords($encoded, sha1("saltA".$raw."saltB"))) {
$this->forward('club.password_rehash:rehash');
}
// Test for Symfony's Bcrypt authentication (any passwords just rehashed in previous step should work here)
if (parent::isPasswordValid($cost=13, $encoded,$raw,$salt)) return true ;
}
}
PasswordRehash.php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
class PasswordRehash extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder
{
// Customises BCryptPasswordEncoder class to use legacy SHA method
function rehash($member, $raw, $salt)
{
//Salt is null as Symfony documentation says it is better to generate a new one
parent::encodePassword($member->getPlainPassword, $salt=null ) ;
}
}
Some other previous attempts for completeness:
My guess is that the problem is that I am misunderstanding what objects are available to me. My understanding is that the user hasn't been authenticated at this point so have tried and removed the below attempts:
Trying to inject the $member into the HubAuthenticator service:
function __construct($cost=13)
{
parent::__construct($cost, \Member $member);
}
When trying to get the plainpassword to rehash:
$this->get('security.context')->getToken()->getUser()->getPlainPassword();
In your services, you can only access what dependencies you've injected.
So, to access the current user object, you need to pass it as argument:
service:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage" ]
Constructor:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class HubAuthenticator extends \Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder implements PasswordEncoderInterface
{
private $storage;
function __construct($cost = 13, TokenStorageInterface $storage)
{
parent::__construct($cost);
$this->storage = $storage;
// Now you can use:
// $user = $this->storage->getToken()->getUser();
}
}
Then, to access the second service, same way, inject it.
Add it to the service arguments:
club.password_rehash:
class: AppBundle\Service\PasswordRehash
arguments: [ "#security.token_storage", "#club.password_rehash" ]
Add it to your constructor:
private $storage;
private $passwordRehash
function __construct($cost = 13, TokenStorageInterface $storage, PasswordRehash $passwordRehash)
{
parent::__construct($cost);
$this->storage = $storage;
$this->passwordRehash = $passwordRehash;
// Now you can use:
// $this->passwordRehash->rehash(...);
}
Hope this helps you.
I start with a seeded database and am trying to reseed the database between unit tests in Laravel 5. In Laravel 4 I understand you could simply use Illuminate\Support\Facades\Artisan and run the commands
Artisan::call('migrate');
Artisan::call('db:seed');
or you supposedly could do:
$this->seed('DatabaseSeeder');
before every test. In Laravel 5 this appears to have been replaced by
use DatabaseMigrations;
or
use DatabaseTransactions;
I have tried using these and have managed to get the tests to migrate the database; however, it doesn't actually reseed the data in the tables. I have read through several forums complaining about this and have tried several different approaches calling these from the TestCase and inside every Test...adding the
$this->beforeApplicationDestroyed(function () {
Artisan::call('migrate');
Artisan::call('migrate:reset');
Artisan::call('db:seed');
DB::disconnect();
});
to the TestCase.php tearDown()...
I have also tried adding
$this->createApplication();
to a method called in every test from TestCase.php
Sometimes it just wipes my tables out completely. Nothing I am finding on Laravel's site or in blogs seems to work. Part of it is probably because I'm probably trying Laravel 4 methods in Laravel 5. Is there any way to do this in Laravel 5?
My code for the testcase.php looks like:
<?php
use Illuminate\Support\Facades\Artisan as Artisan;
class TestCase extends Illuminate\Foundation\Testing\TestCase{
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
protected $baseUrl = 'http://localhost';
public function initializeTests(){
$this->createApplication();
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
$this->session(['test' => 'session']);
$this->seed('DatabaseSeeder');
}
public function tearDown()
{
Mockery::close();
Artisan::call('migrate:reset');
$this->artisan('migrate:reset');
Artisan::call('migrate:rollback');
$this->artisan('migrate:rollback');
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
DB::disconnect();
foreach (\DB::getConnections() as $connection) {
$connection->disconnect();
}
$this->beforeApplicationDestroyed(function () {
Artisan::call('migrate:reset');
$this->artisan('migrate:reset');
Artisan::call('migrate:rollback');
$this->artisan('migrate:rollback');
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
DB::disconnect();
foreach (\DB::getConnections() as $connection) {
$connection->disconnect();
}
});
$this->flushSession();
parent::tearDown();
}
public function getConnection()
{
$Connection = mysqli_connect($GLOBALS['DB_DSN'], $GLOBALS['DB_USERNAME'], $GLOBALS['DB_PASSWORD'], $GLOBALS['DB_DATABASE']);
$this->createDefaultDBConnection();
return $this->Connection;
}
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
/**
* Magic helper method to make running requests simpler.
*
* #param $method
* #param $args
* #return \Illuminate\Http\Response
*/
public function __call($method, $args)
{
if (in_array($method, ['get', 'post', 'put', 'patch', 'delete']))
{
return $this->call($method, $args[0]);
}
throw new BadMethodCallException;
}
/**
* Create a mock of a class as well as an instance.
*
* #param $class
* #return \Mockery\MockInterface
*/
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
}
My Test looks something like
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Artisan;
class CustomerRegistrationControllerTest extends TestCase
{
use DatabaseMigrations;
protected static $db_inited = false;
protected static function initDB()
{
echo "\n---Customer Registration Controller Tests---\n"; // proof it only runs once per test TestCase class
Artisan::call('migrate');
Artisan::call('db:seed');
}
public function setUp()
{
parent::setUp();
if (!static::$db_inited) {
static::$db_inited = true;
static::initDB();
}
// $this->app->refreshApplication();
$this->artisan('migrate:refresh');
$this->seed();
$this->seed('DatabaseSeeder');
$this->initializeTests();
);
}
public function testSomething()
{
$this->Mock
->shouldReceive('destroy')
->with('1')
->andReturn();
$this->RegistrationController->postRegistration();
// $this->assertResponseStatus(200);
}
}
Just run this:
$this->artisan('migrate:refresh', [
'--seed' => '1'
]);
To avoid changes to the database persisting between tests add use DatabaseTransactions to your tests that hit the database.
Why not create your own command like db:reset.
This command either truncate all your tables or drop/create schema and then migrate.
In your test you then use: $this->call('db:reset') in between your tests
I'm trying to define some status constants in my AppModel to be available in every model. To get the string values of them I want to store them in an array, but when I try to internationalize the string it causes the following error:
Fatal error: Call to undefined function AppModel() in /home/dev/www/test/lib/Cake/Utility/ClassRegistry.php on line 181
My code:
class AppModel extends Model {
const STATUS_INACTIVE = 0;
const STATUS_ACTIVE = 1;
public $statuses = array(
self::STATUS_INACTIVE => __('Inactive'),
self::STATUS_ACTIVE => __('Active')
);
}
I had a look in the core and found that the following line fires up the error:
$instance = new $appModel($settings);
The $appModel($settings) statement causes it, I tried to debug it and got the same error.
Any help or idea to head to the right direction to solve this is appreciated.
I don't think you can define $statuses like that in PHP. The problem is that you cannot define a class property using the result of a function.
http://www.php.net/manual/en/language.oop5.properties.php
Class member variables are called "properties". You may also see them referred to using other terms such as "attributes" or "fields", but for the purposes of this reference we will use "properties". They are defined by using one of the keywords public, protected, or private, followed by a normal variable declaration. This declaration may include an initialization, but this initialization must be a constant value--that is, it must be able to be evaluated at compile time and must not depend on run-time information in order to be evaluated.
If you want to set the property with translated labels, move it to your constructor, which is meant for initialisation of Objects;
class AppModel extends Model {
const STATUS_INACTIVE = 0;
const STATUS_ACTIVE = 1;
public $statuses;
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->statuses = array(
self::STATUS_INACTIVE => __('Inactive'),
self::STATUS_ACTIVE => __('Active')
);
}
}
Or move it to another method so that is is only generated when actually used, like this;
public function getStatusOptions()
{
return array(
self::STATUS_INACTIVE => __('Inactive'),
self::STATUS_ACTIVE => __('Active')
);
}
On another note; a nice trick to define class constants and make them re-usable, without adding them to the AppModel is by using an interface;
interface ActiveInactive {
const STATUS_INACTIVE = 0;
const STATUS_ACTIVE = 1;
}
interface FooBar {
const HELLO = 'world';
}
class MyModel extends AppModel implements ActiveInactive, FooBar {
public function test()
{
echo self::STATUS_INACTIVE; // outputs '1'
echo self::HELLO; // outputs 'world'
}
}
class MyHelper extends Helper implements ActiveInactive, FooBar {
public function test()
{
echo self::STATUS_INACTIVE; // outputs '1'
echo self::HELLO; // outputs 'world'
}
}
This way you can re-use those constants anywhere; in Helpers, Models, Controllers. And you can 'combine' those constants :)