Dynamically created datasource not being passed to

2019-04-13 20:51发布

问题:

I have a Model - Car - the Car has several associated models, lets consider one of them which is linked with the hasMany relationship - Wheel

In my CarsController, I dynamically generate a datasource using the following code -

$schemaName = $this->Session->read('User.schema'); 
$config = ConnectionManager::getDataSource('default')->config;
$config['database'] = $schemaName;
ConnectionManager::create($schemaName, $config);

Then I set this datasource in my Car Model using the following line of code

$this->Car->setDataSource($schemaName);

After this I am able to query and operate on Car, however, if I try to operate on Wheel using the following statements - I get an error

$this->Car->Wheel->create();
$this->Car->Wheel->save($wheelData);

The error I get is - Error: [MissingTableException] Table wheels for model Wheel was not found in datasource default.

For some reason the datasource is not being passed from Parent model to associated child models. If I explicitly set the datasource in Wheel using the following line then everything works fine.

$this->Car->Wheel->setDataSource($schemaName);

Can anyone help shed some light on this behavior and how to fix this? The reason I find this inconvenient is that my parent model has several associated models (which further have associated models) and setting datasource individually on each of them doesnt sound right.

Side Question - is there a way to check if a datasource already exists before trying to create one dynamically? I have a for-loop that wraps this entire code and each loop iteration will end up creating a new datasource

I am using CakePHP 2.5.4

回答1:

Here is some code which will pass the setDatasource() call to associated models, if you want only the Car model to pass through its datasource put this code in the Car model, if you want all models to pass through, put it in AppModel.php

public function setDatasource($datasource = null) {
    foreach (array_keys($this->getAssociated()) as $modelName) {
        $this->{$modelName}->setDatasource($datasource);
    }
    parent::setDatasource($datasource);
}

To answer your comment, I would add public $dynamicDb = false in AppModel.php, meaning false would be the default value for this variable, then in your models override it by adding public $dynamicDb = true, then change the above function to:

public function setDatasource($datasource = null) {
    foreach (array_keys($this->getAssociated()) as $modelName) {
        if ($this->{$modelName}->dynamicDb === true) {
            $this->{$modelName}->setDatasource($datasource);
        }
    }
    parent::setDatasource($datasource);
}

(I haven't tested this amended function as I'm not on my dev PC right now, but its a fairly simple change and it should work)

To check if a datasource already exists before you create one, I can see two possible methods, one is by calling ConnectionManager::getDatasource($schemaName) and catching the exception if the datasource does not exist, or call ConnectionManager::sourceList() and check if your $schemaName is in the returned array, here's an implementation of the first option:

$schemaName = $this->Session->read('User.schema'); 
try {
    ConnectionManager::getDatasource($schemaName)
} catch (MissingDatasourceException $e) {
    $config = ConnectionManager::getDataSource('default')->config;
    $config['database'] = $schemaName;
    ConnectionManager::create($schemaName, $config);
}

and the second option:

$datasources = ConnectionManager::sourceList();
$schemaName = $this->Session->read('User.schema'); 
if (!in_array($schemaName, $datasources)) {
    $config = ConnectionManager::getDataSource('default')->config;
    $config['database'] = $schemaName;
    ConnectionManager::create($schemaName, $config);
}

Hope this helps