CakePHP switch database (using same datasource) on the fly? - php

I have tried to build a small function that would be use in controller to switch database on the fly, i must use only one datasource.
On my database.php :
function __construct() {
$server = Configure::read('Server');
if(!empty($server['database'])) $this->local['database'] = $server['database'];
$this->default = $this->{$server['datasource']};
}
Is used to switch database depending on server config. It is working great.
I tried to build up this :
/**
* Connects to specified database
*
* #param array $config Server config to use {datasource:?, database:?}
* #return array db->config on success, false on failure
* #access public
*/
function dbConnect($config = array()) {
ClassRegistry::init('ConnectionManager');
//debug($config['datasource']);
//$dbInstance =& ConnectionManager::getInstance();
//$dbInstance->config->{$config['datasource']}['database'] = $config['database'];
$db =& ConnectionManager::getDataSource($config['datasource']);
$db->disconnect();
$db->cacheSources = false;
$db->config['database'] = $config['database'];
$db->config['persistent'] = false;
debug($db->config);
$db->connect();
if(!$db->isConnected()) {
$this->error('!$db->isConnected()');
return false;
}
return $db->config;
}
But sadly, everything seems to work but i alwas get data from the same DB using $this->Player->find('list') for instance. I tried $this->Player->cacheQueries = false; with no more success.

Made it work using this (create a new connection on the fly) :
$newDbConfig = $this->dbConnect($serverConfig);
$this->Model->useDbConfig = $newDbConfig['name'];
$this->Model->cacheQueries = false;
With :
/**
* Connects to specified database
*
* #param array $config Server config to use {datasource:?, database:?}
* #return array db->config on success, false on failure
* #access public
*/
function dbConnect($config = array()) {
ClassRegistry::init('ConnectionManager');
$nds = $config['datasource'] . '_' . $config['database'];
$db =& ConnectionManager::getDataSource($config['datasource']);
$db->setConfig(array('name' => $nds, 'database' => $config['database'], 'persistent' => false));
if($ds = ConnectionManager::create($nds, $db->config)) return $db->config;
return false;
}

I just wanted to tell that nowadays it's really simple to change current model's datasource (database connection):
For example in my PersonsController index:
public function index() {
//change to a different datasource which is already written in App/Config/database.php-file
$this->Person->setDataSource('testdb2');
$persons = $this->Person->find('all');
$this->set('db1persons', $persons);
//change to a different database
$this->Person->setDataSource('testdb');
$persons = $this->Person->find('all');
$this->set('db2persons', $persons);
}
As you can see the key here is to use $this->Model->setDataSource()

Related

How to add a filter to a result set?

I am using FOSElasticaBundle and Symfony. I have a method to return some results merged from two types of objects:
public function getMergedResults($query)
{
//See https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/doc/cookbook/multi-type-search.md
$indexManager = $this->get('fos_elastica.index_manager');
$transformer = $this->get('app.elastica_to_model.transformer.snapshot');
/* #var \Elastica\Search $search */
$search = $indexManager->getIndex('app')->createSearch();
$search->addType('snapshot');
$search->addType('traffic_company');
$search->setQuery($query);
$resultSet = $search->search();
$transformer = $this->get('fos_elastica.elastica_to_model_transformer.collection.app');
$resultSet = $transformer->transform($resultSet->getResults());
return $resultSet;
}
... which works well. Now I want to add a filter that was left to me by a previous developer:
protected function addSiteFilter(Query\BoolQuery $boolFilter, SiteInterface $site = null)
{
if ($site) {
$termFilter = new Query\Term(array('site' => array('value' => $site->getId())));
$boolFilter->addMust(array(
$termFilter
));
}
}
... but I'm not sure how to glue these two pieces together. What code can I add to the first function so that it integrates the second one?
I'll provide an example, If it wouldn't be so clear for you, ping me.
/**
* #param string $ownerId
* #return Query
*/
public function getByOwnerId(string $ownerId): Query
{
$terms = new Terms('ownerId', [$ownerId]);
$boolQuery = (new BoolQuery())->addMust($terms);
return (new Query())->setQuery($boolQuery);
}

Magento2 addFieldToFilter call works with hardcoded value but not variable with same value

I'm building the admin for a Magento2 store (currently on 2.1.7, they want to use the newest version until we go live and then want to stabilize a particular version). The module in question is supposed to display all existing orders, with an actionsColumn that contains links to cancel, edit, and open a detailed overview of the purchased items associated with that order. The order detail page contains a grid view that should display all order items associated with an order number passed in the URL.
In order to filter out Order Items that don't relate to the specific Order Number, I've extended the \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult class. This works except for one weird caveat. If, in the addFieldToFilter call, I replace $ordNum with, say, '10000', it grabs the correct data. When using $ordNum to call this dynamically, however, it returns no rows at all. This despite trying all sorts of casting and === checks to ensure that there's no difference between the hardcoded and dynamic values. Is this a Magento bug? I can't at all figure out why this would be the case.
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$ordNum = $request->getParam('order_num');
$this->addFieldToFilter('order_num', ['eq' => $ordNum]); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
I just fixed it by using mentioned below steps
Store param value in session in controller
public function execute() {
$this->_catalogSession->setTokenId($this->request->getParam('entity_id'));
$this->_view->loadLayout();
$this->_view->loadLayoutUpdates();
$this->_view->getPage()->getConfig()->getTitle()->set(__('Redeem Token History'));
$this->_view->renderLayout();
}
Use session value in dataprovider
$tokensCollection->addFieldToFilter('token_id', ['eq' => $this->_catalogSession->getTokenId()]);
Enjoy :)
Try this in place of the getParam statement:
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
Just to make sure we are on the same page, this is the full code:
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
$this->addFieldToFilter('order_num', $ordNum); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
We have solved this issue by doing the following :
/**
* CcCustompriceProductListingDataProvider constructor.
* #param string $name
* #param string $primaryFieldName
* #param string $requestFieldName
* #param \Magento\Framework\Api\Search\ReportingInterface $reporting
* #param \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder
* #param \Magento\Framework\App\RequestInterface $request
* #param \Magento\Framework\Api\FilterBuilder $filterBuilder
* #param array $meta
* #param array $data
* #throws \Exception
*/
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
ReportingInterface $reporting,
SearchCriteriaBuilder $searchCriteriaBuilder,
RequestInterface $request,
FilterBuilder $filterBuilder,
array $meta = [],
array $data = []
) {
$data['config']['filter_url_params']['product_id'] = $request->getParam('cppc_product_id', 0);
parent::__construct($name, $primaryFieldName, $requestFieldName, $reporting, $searchCriteriaBuilder, $request, $filterBuilder, $meta, $data);
}
You do not need to use any other function. The reason why this is is because it is also updated with an update URL and that does not have that parameter. By using adding that to the data it also parses that into the update url.
You can see that here (Parent function)
/**
* #return void
*/
protected function prepareUpdateUrl()
{
if (!isset($this->data['config']['filter_url_params'])) {
return;
}
foreach ($this->data['config']['filter_url_params'] as $paramName => $paramValue) {
if ('*' == $paramValue) {
$paramValue = $this->request->getParam($paramName);
}
if ($paramValue) {
$this->data['config']['update_url'] = sprintf(
'%s%s/%s/',
$this->data['config']['update_url'],
$paramName,
$paramValue
);
$this->addFilter(
$this->filterBuilder->setField($paramName)->setValue($paramValue)->setConditionType('eq')->create()
);
}
}
}

cakephp tcpd error: Some data has already been output to browser, can't send PDF file on remote server

I'm getting the following warning when try to view list_of_holidays.pdf from the remote server:
Warning (2): Cannot modify header information - headers already sent by (output started
at /home/aquinto1/app/views/helpers/flash.php:155) [APP/vendors/tcpdf/tcpdf.php, line 8541]
TCPDF ERROR: Some data has already been output to browser, can't send PDF file
line 155 is the last line in flash.php ie the closing tag for php (?>). Before that it is the code to embedSWF. I don't see anything wrong with that.
However, it is displaying fine on the local server.
I've checked for whitespaces and yet the error is still there.
i'm already using ob_clean before the output.
can someone tell me on what i'm doing wrong. FYI i'm using cakephp with tcpdf.
The following is flash.php
class FlashHelper extends AppHelper {
var $helpers = array('Javascript');
/**
* Used for remembering options from init() to each renderSwf
*
* #var array
*/
var $options = array(
'width' => 100,
'height' => 100
);
/**
* Used by renderSwf to set a flash version requirement
*
* #var string
*/
var $defaultVersionRequirement = '9.0.0';
/**
* Used by renderSwf to only call init if it hasnt been done, either
* manually or automatically by a former renderSwf()
*
* #var boolean
*/
var $initialized = false;
/**
* Optional initializing for setting default parameters and also includes the
* swf library. Should be called once, but if using several groups of flashes,
* MAY be called several times, once before each group.
*
* #example echo $flash->init();
* #example $flash->init(array('width'=>200,'height'=>100);
* #return mixed String if it was not able to add the script to the view, true if it was
*/
function init($options = array()) {
if (!empty($options)) {
$this->options = am($this->options, $options);
}
$this->initialized = true;
$view =& ClassRegistry::getObject('view');
if (is_object($view)) {
$view->addScript($this->Javascript->link('swfobject'));
return true;
} else {
return $this->Javascript->link('swfobject');
}
}
/**
* Wrapper for the SwfObject::embedSWF method in the vendor. This method will write a javascript code
* block that calls that javascript method. If given a dom id as fourth parameter the flash will
* replace that dom object. If false is given, a div will be placed at the point in the
* page that this method is echo'ed. The last parameter is mainly used for sending in extra settings to
* the embedding code, like parameters and attributes. It may also send in flashvars to the flash.
*
* For doucumentation on what options can be sent, look here:
* http://code.google.com/p/swfobject/wiki/documentation
*
* #example echo $flash->renderSwf('counter.swf'); // size set with init();
* #example echo $flash->renderSwf('flash/ad.swf',100,20);
* #example echo $flash->renderSwf('swf/banner.swf',800,200,'banner_ad',array('params'=>array('wmode'=>'opaque')));
* #param string $swfFile Filename (with paths relative to webroot)
* #param int $width if null, will use width set by FlashHelper::init()
* #param int $height if null, will use height set by FlashHelper::init()
* #param mixed $divDomId false or string : dom id
* #param array $options array('flashvars'=>array(),'params'=>array('wmode'=>'opaque'),'attributes'=>array());
* See SwfObject documentation for valid options
* #return string
*/
function renderSwf($swfFile, $width = null, $height = null, $divDomId = false, $options = array()) {
$options = am ($this->options, $options);
if (is_null($width)) {
$width = $options['width'];
}
if (is_null($height)) {
$height = $options['height'];
}
$ret = '';
if (!$this->initialized) {
$init = $this->init($options);
if (is_string($init)) {
$ret = $init;
}
$this->initialized = TRUE;
}
$flashvars = '{}';
$params = '{wmode : "opaque"}';
$attributes = '{}';
if (isset($options['flashvars'])) {
$flashvars = $this->Javascript->object($options['flashvars']);
}
if (isset($options['params'])) {
$params = $this->Javascript->object($options['params']);
}
if (isset($options['attributes'])) {
$attributes = $this->Javascript->object($options['attributes']);
}
if ($divDomId === false) {
$divDomId = uniqid('c_');
$ret .= '<div id="'.$divDomId.'"></div>';
}
if (isset($options['version'])) {
$version = $options['version'];
} else {
$version = $this->defaultVersionRequirement;
}
if (isset($options['install'])) {
$install = $options['install'];
} else {
$install = '';
}
$swfLocation = $this->webroot.$swfFile;
$ret .= $this->Javascript->codeBlock(
'swfobject.embedSWF
("'.$swfLocation.'", "'.$divDomId.'", "'.$width.'", "'.$height.'", "'.$version.'",
"'.$install.'", '.$flashvars.', '.$params.', '.$attributes.');');
return $ret;
}
}
?>
Simply do what the error tells you: Check app/views/helpers/flash.php line 155 and see what it is outputting there and fix it. There must be some code that outputs something.
Could it be one of the return statements?
if (is_object($view)) {
$view->addScript($this->Javascript->link('swfobject'));
return true;
} else {
return $this->Javascript->link('swfobject');
}
$ret .= $this->Javascript->codeBlock(
'swfobject.embedSWF
("'.$swfLocation.'", "'.$divDomId.'", "'.$width.'", "'.$height.'", "'.$version.'",
"'.$install.'", '.$flashvars.', '.$params.', '.$attributes.');');
return $ret;
}
What other code is on the page calling flash?

Why is this Zend Framework _redirect() call failing?

I am developing a Facebook app in Zend Framework. In startAction() I am getting the following error:
The URL http://apps.facebook.com/rails_across_europe/turn/move-trains-auto is not valid.
I have included the code for startAction() below. I have also included the code for moveTrainsAutoAction (these are all TurnController actions) I can't find anything wrong with my _redirect() in startAction(). I am using the same redirect in other actions and they execute flawlessly. Would you please review my code and let me know if you find a problem? I appreciate it! Thanks.
public function startAction() {
require_once 'Train.php';
$trainModel = new Train();
$config = Zend_Registry::get('config');
require_once 'Zend/Session/Namespace.php';
$userNamespace = new Zend_Session_Namespace('User');
$trainData = $trainModel->getTrain($userNamespace->gamePlayerId);
switch($trainData['type']) {
case 'STANDARD':
default:
$unitMovement = $config->train->standard->unit_movement;
break;
case 'FAST FREIGHT':
$unitMovement = $config->train->fast_freight->unit_movement;
break;
case 'SUPER FREIGHT':
$unitMovement = $config->train->superfreight->unit_movement;
break;
case 'HEAVY FREIGHT':
$unitMovement = $config->train->heavy_freight->unit_movement;
break;
}
$trainRow = array('track_units_remaining' => $unitMovement);
$where = $trainModel->getAdapter()->quoteInto('id = ?', $trainData['id']);
$trainModel->update($trainRow, $where);
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto');
}
.
.
.
public function moveTrainsAutoAction() {
$log = Zend_Registry::get('log');
$log->debug('moveTrainsAutoAction');
require_once 'Train.php';
$trainModel = new Train();
$userNamespace = new Zend_Session_Namespace('User');
$gameNamespace = new Zend_Session_Namespace('Game');
$trainData = $trainModel->getTrain($userNamespace->gamePlayerId);
$trainRow = $this->_helper->moveTrain($trainData['dest_city_id']);
if(count($trainRow) > 0) {
if($trainRow['status'] == 'ARRIVED') {
// Pass id for last city user selected so we can return user to previous map scroll postion
$this->_redirect($config->url->absolute->fb->canvas . '/turn/unload-cargo?city_id='.$gameNamespace->endTrackCity);
} else if($trainRow['track_units_remaining'] > 0) {
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto');
} else { /* Turn has ended */
$this->_redirect($config->url->absolute->fb->canvas . '/turn/end');
}
}
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto-error'); //-set-destination-error');
}
As #Jani Hartikainen points out in his comment, there is really no need to URL-encode underscores. Try to redirect with literal underscores and see if that works, since I believe redirect makes some url encoding of its own.
Not really related to your question, but in my opinion you should refactor your code a bit to get rid of the switch-case statements (or at least localize them to a single point):
controllers/TrainController.php
[...]
public function startAction() {
require_once 'Train.php';
$trainTable = new DbTable_Train();
$config = Zend_Registry::get('config');
require_once 'Zend/Session/Namespace.php';
$userNamespace = new Zend_Session_Namespace('User');
$train = $trainTable->getTrain($userNamespace->gamePlayerId);
// Add additional operations in your getTrain-method to create subclasses
// for the train
$trainTable->trackStart($train);
$this->_redirect(
$config->url->absolute->fb->canvas . '/turn/move-trains-auto'
);
}
[...]
models/dbTable/Train.php
class DbTable_Train extends Zend_Db_Table_Abstract
{
protected $_tableName = 'Train';
[...]
/**
*
*
* #return Train|false The train of $playerId, or false if the player
* does not yet have a train
*/
public function getTrain($playerId)
{
// Fetch train row
$row = [..];
return $this->trainFromDbRow($row);
}
private function trainFromDbRow(Zend_Db_Table_Row $row)
{
$data = $row->toArray();
$trainType = 'Train_Standard';
switch($row->type) {
case 'FAST FREIGHT':
$trainType = 'Train_Freight_Fast';
break;
case 'SUPER FREIGHT':
$trainType = 'Train_Freight_Super';
break;
case 'HEAVY FREIGHT':
$trainType = 'Train_Freight_Heavy';
break;
}
return new $trainType($data);
}
public function trackStart(Train $train)
{
// Since we have subclasses here, polymorphism will ensure that we
// get the correct speed etc without having to worry about the different
// types of trains.
$trainRow = array('track_units_remaining' => $train->getSpeed());
$where = $trainModel->getAdapter()->quoteInto('id = ?', $train->getId());
$this->update($trainRow, $where);
}
[...]
/models/Train.php
abstract class Train
{
public function __construct(array $data)
{
$this->setValues($data);
}
/**
* Sets multiple values on the model by calling the
* corresponding setter instead of setting the fields
* directly. This allows validation logic etc
* to be contained in the setter-methods.
*/
public function setValues(array $data)
{
foreach($data as $field => $value)
{
$methodName = 'set' . ucfirst($field);
if(method_exists($methodName, $this))
{
$this->$methodName($value);
}
}
}
/**
* Get the id of the train. The id uniquely
* identifies the train.
* #return int
*/
public final function getId ()
{
return $this->id;
}
/**
* #return int The speed of the train / turn
*/
public abstract function getSpeed ();
[..] //More common methods for trains
}
/models/Train/Standard.php
class Train_Standard extends Train
{
public function getSpeed ()
{
return 3;
}
[...]
}
/models/Train/Freight/Super.php
class Train_Freight_Super extends Train
{
public function getSpeed ()
{
return 1;
}
public function getCapacity ()
{
return A_VALUE_MUCH_LARGER_THAN_STANDARD;
}
[...]
}
By default, this will send an HTTP 302 Redirect. Since it is writing headers, if any output is written to the HTTP output, the program will stop sending headers. Try looking at the requests and response inside Firebug.
In other case, try using non default options to the _redirect() method. For example, you can try:
$ropts = { 'exit' => true, 'prependBase' => false };
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto', $ropts);
There is another interesting option for the _redirect() method, the code option, you can send for example a HTTP 301 Moved Permanently code.
$ropts = { 'exit' => true, 'prependBase' => false, 'code' => 301 };
$this->_redirect($config->url->absolute->fb->canvas . '/turn/move-trains-auto', $ropts);
I think I may have found the answer. It appears that Facebook does not play nice with redirect, so it is neccessary to use Facebook's 'fb:redirect' FBML. This appears to work:
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender();
echo '<fb:redirect url="' . $config->url->absolute->fb->canvas . '/turn/move-trains-auto"/>';

How to Make Zend_Controller_Router resets un-wanted parameter by default

I'm using Zend-Framework 1.9.5 to make a web-application, But it's Url_Helper was quite tricky to me in the matter of parameter reset!, I know it's a good feature (parameter preserving) but in most cases I don't need it!.
So I'm thinking of overriding the default Router to force it loosing parameters Unless I ask for it or maybe specifying a certain parameters that it keeps like (lang, or something like that).
Also I want to make it the default router so I don't have to edit my Controllers, Views to get that done!
Any suggestions?
Update:
I spent the whole morning trying to write my url helper Admin_View_Helper_Xurl, But I couldn't do anything that solves the problem:
<?php
class Admin_View_Helper_Xurl extends Zend_View_Helper_Abstract
{
public function xurl(array $urlOptions = array(), $name = 'default', $reset = false, $encode = true)
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$wanted_params = array('module', 'controller', 'action', 'lang', 'page', 'search');
$route = $router->getCurrentRoute();
$something = anyWayToGetThatObjectOrClass();
$params = $something->getParams();
foreach($params as $key => $val) {
if (!in_array($key, $wanted_params)) {
$params[$key] = null; // OR uset($params[$key]);
}
}
$something->clearParams();
$something->setParams($params);
return $router->assemble($urlOptions, $name, $reset, $encode);
}
}
I tried to get current URL parameters and filter them and clear the current parameters and pass my filtered ones but I couldn't do anything that does it without hard-code editing one Zend_Framework code :(.
Thanks
When generating a link a view, you can ask the helper to get rid of all aparamters with a simple boolean :
<?php echo $this->url(array('controller' => 'index', action => 'action'), 'default', true); ?>
The last parameter tells whether to reset parameters or not.
I came up with this solution. It took 7 hours to be functional.
class Zend_View_Helper_Xurl extends Zend_View_Helper_Abstract
{
const RESET_ALL = 'all';
const RESET_CUSTOM = 'normal';
const RESET_NON_MVC = 'mvc';
const RESET_NONE = 'none';
protected $_wantedParams = array('module', 'controller', 'action', 'lang', 'page', 'search');
protected $_router;
/**
* Generates an url given the name of a route.
*
* #access public
*
* #param array $urlOptions Options passed to the assemble method of the Route object.
* #param mixed $name The name of a Route to use. If null it will use the current Route
* #param bool $reset Whether or not to reset the route defaults with those provided
* #return string Url for the link href attribute.
*/
public function __construct()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$this->_router = clone $router;
}
public function xurl(array $urlOptions = array(), $reset = 'mvc', $encode = true)
{
$urlOptions = $this->_getFilteredParams($urlOptions, $reset);
return $this->_router->assemble($urlOptions, $name, true, $encode);
}
protected function _getFilteredParams($data = array(), $level)
{
// $filteredValues = array();
$request = Zend_Controller_Front::getInstance()->getRequest();
$filteredValues = $request->getUserParams();
$$filteredValues['module'] = $request->getModuleName();
$$filteredValues['controller'] = $request->getControllerName();
$$filteredValues['action'] = $request->getActionName();
switch ($level) {
case self::RESET_ALL:
$filteredValues['module'] = null;
$filteredValues['controller'] = null;
$filteredValues['action'] = null;
// break omitted intentionally
case self::RESET_NON_MVC:
$filteredValues['page'] = null;
$filteredValues['lang'] = null;
$filteredValues['search'] = null;
// break omitted intentionally
case self::RESET_CUSTOM:
foreach ($filteredValues as $key=>$val) {
if (!in_array($key, $this->_wantedParams)) {
$filteredValues[$key] = null;
}
}
break;
case self::RESET_NONE:
break;
default:
throw new RuntimeException('Unsuppoted Xurl URL helper reset level.');
break;
}
foreach ($filteredValues as $key => $val) {
if (!array_key_exists($key, $data)) {
$data[$key] = $val;
}
}
return $data;
}
}
Clearly it's a View Helper class, may be not the best solution but it works fine with me for now.

Categories