Using Multiple Database Connection Not Working For Extension - yii-extensions

I am doing multiple database connection using the tutorial at http://www.yiiframework.com/wiki/544/multiple-database-connection-select-database-based-on-login-user-id-dynamic/ . The code is working fine in the model. But the problem is I am using an extension where I am using db connection using Yii::app()->db; Here I am getting exception Property "CWebApplication.dbadvert" is not defined. The controller of the extension is extended from CExtController. Please help.

In the example you're referring dbadvert is set up for custom active record class RActiveRecord, not for the web application.
If you want to use it like Yii::app()->dbadvert, you would need to set it up in components section of your config.php like this
'dbadvert' => array(
'class' => 'CDbConnection'
*params here*
),
UPD
You can create a wrapper component for CDbConnection, that will change the connection string any way you want and put in as a webapp component.
<?php
class CMultiuserDatabaseConnection extends CApplicationComponent {
public function __call($name, $params) {
$db = $this->db;
return call_user_func_array(($db, $name), $params);
}
public $dbConnectionClass = 'CDbConnection';
public $connections = null;
public $defaultConfiguration = null;
public function getDatabaseConfiguration($user) {
if (!$this->connections) { return array(); }
return array_key_exists($user, $this->connections) ? $this->connections[$user] : $this->defaultConfiguration;
}
public function getDb() {
$user = Yii::app()->user;
if ($user->isGuest) { return false; }
$username = $user->name;
$config = $this->getDatabaseConfiguration($username);
if (!$config) { return false; }
$dsn = array_key_exists('dsn', $config) ? $config['dsn'] : null;
if (!$dsn) { return false; }
$user = array_key_exists('user', $config) ? $config['user'] : null;
$password = array_key_exists('password', $config) ? $config['password'] : null;
$result = new $this->dbConnectionClass($dsn, $user, $password);
return $result;
}
}
That's a crude example of component, which you can set up as your 'db' component and then you'll get 'connections' option for storing the per-user configuration in that way:
'components' => array(
...
'db' => array(
'class' => "CMultiuserDatabaseConnection",
'connections' => array(
"first-user-name" => array(
// just another db configuration here, for user-one
),
"second-user-name" => array(
// just another db configuration herer, for user two
),
...
),
'defaultConfiguration' => array(
/*
* here goes configuration for all other user, that were not specified
* in connections.
*/
),
),
...
),

I wrote the query for the extension in a model as functions. and in the CExtController created an instance of the model. Then I called those functions and everything is working fine.

Related

Symfony 3.3 CraueFormFlowBundle Request_stack is empty

my first question to this site is a little difficult to describe.
I am quite new to Symfony, startet with 3.2 and updated recently to 3.3.5 (not sure if relevant for the problem).
I tried to use CraueFormFlowBundle (multistep form bundle) but cannot get it to work.
The problem is that trying to access the flow results in an exception:
Error: Call to a member function getCurrentRequest() on null
Symfony\Component\Debug\Exception\ FatalErrorException
in vendor/craue/formflow-bundle/Form/FormFlow.php (line 191)
Line 191 shows: $currentRequest = $this->requestStack->getCurrentRequest();
Modifying the FormFlow.php with dump line shows that $this->requestStack is null.
I have not enough knowledge about this bundle to know where to start looking for the problem.
The flow definition is based on the location example:
namespace EngineeringBundle\Form;
use Craue\FormFlowBundle\Form\FormFlow;
use Craue\FormFlowBundle\Form\FormFlowInterface;
class SelectExaminationFlow extends FormFlow
{
/**
* {#inheritDoc}
*/
protected function loadStepsConfig()
{
dump("loadStepsConfig");
return array(
array(
'label' => 'engineering.discipline',
'form_type' => new SelectExaminationStep1Form(),
),
array(
'label' => 'engineering.date',
'form_type' => new SelectExaminationStep2Form(),
'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) {
return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveRegion();
},
),
array(
'label' => 'confirmation',
),
);
}
The form definition is also quite simple and works without problems:
class SelectExaminationStep1Form extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
dump("buildForm");
$builder
->add('id', HiddenType::class)
->add('discipline', EntityType::class, array(
'class' => 'EngineeringBundle:Discipline',
'choice_label' => 'disciplineName',
'label' => 'engineering.discipline.label'
)
);
}
public function getName() {
return $this->getBlockPrefix();
}
public function getBlockPrefix() {
return 'createEngineeringStep1';
}
}
services.yml:
EngineeringBundle\Form\SelectExaminationFlow:
parent: craue.form.flow
autowire: false
autoconfigure: false
public: true
engineering.form_flow:
alias: EngineeringBundle\Form\SelectExaminationFlow
public: true
Controller:
/**
* #Route("create", name="engineering_create")
*/
public function createAction()
{
return $this->processFlow(new ExaminationDate(), $this->get('engineering.form_flow'));
}
Thanks in advance
Sebastian
I was having the same problem, resolved it by adding a constructor to vendor/craue/formflow-bundle/Form/FormFlow.php with the following content:
public function __construct(RequestStack $requestStack, FormFactoryInterface $formFactory, DataManagerInterface $dataManager, EventDispatcherInterface $eventDispatcher) {
$this->formFactory = $formFactory;
$this->requestStack = $requestStack;
$this->dataManager = $dataManager;
$this->eventDispatcher = $eventDispatcher;
}
Make sure to place it after all setter-methods. Problem seems to be related to a symfony update.

How to use a foreign database in extbase/fluid

I have a simple extbase extension (typo3) with one controller and one model/view. Now i want to "select *" a MSSQL database and output the results in my view. I have not found any reference on how to realize this.
How can i connect to a foreign database from within my extbase/fluid extension and use the data from this database (MSSQL)? How do i execute a query on the "extDB" and how to i print out the result IN my fluid-view.
This is how i integrate the DB (dbal/adodb):
$TYPO3_CONF_VARS['EXTCONF']['dbal']['handlerCfg'] = array(
'extDB' => array(
'type' => 'adodb',
'config' => array(
'driver' => 'mssql',
'username' => 'DB_username',
'password' => 'DB_password',
'host' => 'DB_host',
'database' => 'DB_used',
)
)
);
$TYPO3_CONF_VARS['EXTCONF']['dbal']['table2handlerKeys'] = array (
'VIEW_TABLE1' => 'extDB',
'VIEW_TABLE2' => 'extDB',
);
Any help is appreciated.
You can try extension Database Abstraction Layer (dbal).
Example use global variable $GLOBALS. This is tested on Typo3 4.x, but with little updates(?) maybe working on Typo 6.2 (eg. remove deprecated t3lib_div, and use GeneralUtility...).
class Abstract {
/**
* Access to database
*
*/
private
$msdbHost = 'localhost',
$msdb = 'database-name',
$msdbUsername = 'xxx',
$msdbPassword = 'xxx';
/**
* Connect to MS database
*
*/
public function connectDatabaseMs() {
$GLOBALS['MS_DB'] = t3lib_div::makeInstance('t3lib_DB');
$GLOBALS['MS_DB']->sql_pconnect($this->msdbHost, $this->msdbUsername, $this->msdbPassword);
if($GLOBALS['MS_DB']->link) {
if($GLOBALS['MS_DB']->sql_select_db($this->msdb)) {
return $GLOBALS['MS_DB'];
} else {
t3lib_div::devLog('[tx_rtgms_lib::connectDatabaseUsers] Can not select database "'.$this->msdb.'".', $this->extKey, 3);
}
} else {
t3lib_div::devLog('[tx_rtgms_lib::connectDatabaseUsers] Can not connect to database "'.$this->msdb.'".', $this->extKey, 3);
}
$GLOBALS['MS_DB'] = FALSE;
return FALSE;
}
/**
* Example
*
*/
public function getData() {
$query = 'SELECT ...';
$res = $GLOBALS['HOSTAFF_DB']->sql_query($query);
...
}
}

ZF2 testing - mock object ignored by serviceManager

I'm trying to run tests on my controller in Zend Framework 2.3.
My tested action looks like this:
public function listAction() {
// optional filtering
$filter = $this->params('filter'); // filter type
// params from JSON
$jsonPost = $this->getRequest()->getContent();
$params = json_decode($jsonPost);
$param1 = isset($params->query1) ? $params->query1 : NULL;
$param2 = isset($params->query2) ? $params->query2 : NULL;
$json = Array( // json container
'status' => TRUE,
'data' => Array(),
);
try {
// get data from Model
$list = new Mapping\Books\Listing();
if( !empty($filter)) { // optional filtering
$list->addFilter($filter, $param1, $param2);
}
$data = $list->setOrder()->getList();
// final data mapping to JSON and formating
foreach($data as $isbn => $book) {
$json['data'][$isbn] = Array(
'ISBN' => $book->getISBN(),
'title' => $book->getTitle(),
'rating' => $book->getRating(),
'release_date' => $book->getReleaseDate()->format('F Y'),
'authors' => Array() // multiple
);
// final authors mapping
foreach($book->getAuthors() as $author) {
$json['data'][$isbn]['authors'][] = Array(
'author_id' => $author->getId(),
'author_name' => $author->getName()
);
}
}
} catch(Exception $e) {
$json = Array(
'status' => FALSE,
'error' => $e->getMessage()
);
}
return new JsonModel( $json );
}
My testing method looks like this:
public function testListActionWithoutParams()
{
// object mocking
$listingMock = $this->getMockBuilder('Books\Model\Mapping\Books\Listing')
->disableOriginalConstructor()
->setMethods(Array('getData', 'setOrder'))
->getMock();
$listingMock->expects($this->once())->method('setOrder')
->will($this->returnSelf());
$listingMock->expects($this->once())->method('getData')
->willReturn( Array() ); // for testing is enough
$serviceManager = $this->getApplicationServiceLocator();
$serviceManager->setAllowOverride(true);
$serviceManager->setService('Books\Model\Mapping\Books\Listing', $listingMock);
// dispatch
$this->dispatch('/books');
// routing tests
$this->assertResponseStatusCode(200);
$this->assertModuleName('Books');
$this->assertControllerName('Books\Controller\Index');
$this->assertControllerClass('IndexController');
$this->assertActionName('list');
$this->assertMatchedRouteName('list');
// header tests
$this->assertResponseHeaderContains('Content-type', 'application/json; charset=utf-8');
}
I think I'm missunderstanding something here. My understanding is, that serviceManager will also "notify" autoloader, that actually called class should be replaced by mock object, but it does not.
How can I replace my object with mocked, please ?
Firstly you controller have a lot of logic, what is incorrect.
Secondly, I can give you only workflow of the code:
In your controller you need a method with will be returning list object, so in line:
// get data from Model
$list = new Mapping\Books\Listing();
should be new method call:
// get data from Model
$list = $this->getListing();
And the method:
public function getListing()
{
return new Mapping\Books\Listing();
}
Now you need to mock this method in the controller. And object returned by the mocked method should be your mocked Mapping\Books\Listing object.

passing value from Yii CController class to CForm (Form Builder) config array

I'm new to Yii, and I'm trying to do my initial project the "right" way. I've created a CFormModel class that needs three fields to query for some data, a CForm config to construct the form, and a CController to tie it together (all given below).
The data request needs an account, and this can come from a couple of different places. I think retrieving it should be in the controller. However, I don't know how to get it into the form's hidden "account" field from the controller, so that it makes it to the arguments assigned to the CFormModel after submission. More generally, I know how to pass from CController to view script, but not to CForm. Is the registry (Yii::app()->params[]) my best bet?
I suppose I can just leave it out of the form (and required fields) and wait to populate it in the submit action (actionSummaries). Does that break the intention of CForm? Is there a best practice? Even taking this solution, can someone address the first issue, in case it comes up again?
Any other, gentle critique is welcome.
models/SummariesForm.php
class SummariesForm extends CFormModel
{
public $account;
public $userToken;
public $year;
public function rules () {...}
public function fetchSummary () {...}
static public function getYearOptions () {...}
}
views/account/select.php
<?php
$this->pageTitle=Yii::app()->name;
?>
<div class="form">
<?php echo $form->render(); ?>
</div>
controllers/AccountController.php
class AccountController extends CController
{
public $layout = 'extranet';
public function actionSelect ()
{
$model = new SummariesForm();
// retrieve account
require_once 'AccountCookie.php';
/*
*
* Here, I insert the account directly into the
* model used to build the form, but $model isn't
* available to selectForm.php. So, it doesn't
* become part of the form, and this $model doesn't
* persist to actionSummaries().
*
*/
$model->account = AccountCookie::decrypt();
if ($model->account === false) {
throw new Exception('Unable to retrieve account.');
}
$form = new CForm('application.views.account.selectForm', $model);
$this->render('select', array(
'form' => $form,
'account' => $model->account,
));
}
public function actionSummaries ()
{
$model = new SummariesForm();
if (isset($_POST['SummariesForm'])) {
$model->attributes = $_POST['SummariesForm'];
/*
*
* Should I just omit "account" from the form altogether
* and fetch it here? Does that break the "model"?
*
*/
if ($model->validate() === true) {
try {
$summaries = $model->fetchSummary();
} catch (Exception $e) {
...
CApplication::end();
}
if (count($summaries) === 0) {
$this->render('nodata');
CApplication::end();
}
$this->render('summaries', array('model' => $model, 'summaries' => $summaries));
} else {
throw new Exception('Invalid year.');
}
}
}
}
views/account/selectForm.php
<?php
return array(
'title' => 'Select year',
'action' => Yii::app()->createUrl('Account/Summaries'),
'method' => 'post',
'elements' => array(
'account' => array(
'type' => 'hidden',
'value' => $account,
),
'userToken' => array(
'type' => 'hidden',
'value' => /* get token */,
),
'year' => array(
'type' => 'dropdownlist',
'items' => SummariesForm::getYearOptions(),
),
),
'buttons' => array(
'view' => array(
'type' => 'submit',
'label' => 'View summaries',
),
),
);
The answer is NO to do what you asked. You can see $form variable which acted almost like array when it was passed from controller to view. The solution is you add more property $account into selectForm model and treat it like other elements. I don't think leaving the new field outside the form will be properly way if you want to submit its value also.
Edited:

How to call a custom service on the service locator object from Module#getViewHelperConfig() in Zend Framewok 2?

I have a ViewHelper and want to initialize it in Module#getViewHelperConfig():
<?php
namespace Search;
use statements...
class Module implements
ConfigProviderInterface,
ServiceProviderInterface,
AutoloaderProviderInterface,
ViewHelperProviderInterface {
public function getConfig() ...
public function getAutoloaderConfig() ...
public function getServiceConfig() {
$breakpoint = null;
try {
return array (
'factories' => array(
...
'SearchFormCourseSearchForm' => function ($serviceManager) {
$cacheService = $serviceManager->get('Cache\Model\CityStorage');
$cities = $cacheService->getCities();
$searchForm = new Form\CourseSearchForm($cities);
return $searchForm;
},
)
);
} ...
}
public function getViewHelperConfig() {
$breakpoint = null;
return array(
'factories' => array(
'searhForm' => function($serviceManager) {
$helper = new View\Helper\SearchForm(array('render' => true, 'redirect' => false));
$helper->setViewTemplate('search/search/search-courses');
$searchForm = $serviceManager->get('SearchFormCourseSearchForm');
$helper->setSearchForm($searchForm);
return $helper;
}
)
);
}
But ZF doesn't call my factory. Instead of this it tries to create a new SearchFormCourseSearchForm instance:
Fatal error: Uncaught exception 'Zend\ServiceManager\Exception\ServiceNotFoundException' with message 'Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for SearchFormCourseSearchForm' in /var/www/bar/foo/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php on line 456
How should I use service factories got from Module#getServiceConfig() creating a ViewHelper in Module#getViewHelperConfig()?
You need to get the main service locator. The $serviceManager in getViewHelperConfig() is the View Helper service locator.
To get the main service locator in your getViewHelperConfig() function, do the following:
$maimSm = $serviceManager->getServiceLocator();
Therefore:
public function getViewHelperConfig() {
return array(
'factories' => array(
'searhForm' => function($serviceManager) {
$helper = new View\Helper\SearchForm(array('render' => true, 'redirect' => false));
$helper->setViewTemplate('search/search/search-courses');
$maimSm = $serviceManager->getServiceLocator();
$searchForm = $maimSm->get('SearchFormCourseSearchForm');
$helper->setSearchForm($searchForm);
return $helper;
}
)
);
}
There are quite a few posts explaining this strategy in a bit more details, I'll go hunt for them.
Edit
See Using Zend Framework service managers in your application for a nice description of service managers/locators in ZF2.