CakePHP not finding all fields in table, even after model cache deleted - mysql

So my app is running in development mode in one place and in production mode on its live server. I've just put some changes live and a field that has been in the production DB (MySQL) for a good week or two is not being found by a call to Model::read(). Here's the code, verbatim:
$this->Order->id = $id;
$created = $this->Order->field('created');
$this->Order->contain(array('User', 'OrderStatusChange', 'Cart' => array('CartItem' => array('conditions' => array('deleted_date' => null, 'created <=' => $created)))));
$this->request->data = $order = $this->Order->read();
Same code in dev environment returns all fields. The new(ish) field is missing in production. I have deleted every file in /app/tmp/cache/models and it has not fixed the problem. The production site has Configure::write('debug', 0), development site is set to 2.
Any ideas?
Thanks

As you already cleared your caches, but problem still exists. So, I will point out one things here:
$this->Order->contain(...) here you're using contain() and for contain(), need to attach Containable behavior to model. Your code doesn't clarify that you attach that or not. If not, then I will suggest you to attach that like:
.....
$this->Order->Behaviors->attach('Containable'); // attach Containable behavior
$this->Order->contain(...);
......

Related

Yii2 Multi-Tenant Architecture - Choose correct database

I am building an app using Yii2 and I am working with multi-tenant architecture. So... every client has his own database (identical structure).
What I have done so far:
I declare all the different databases in the config/web.php file
I have a master database that corresponds each user to his database. So, when someone logs in, the app knows what database should use.
What I have done but I am not sure about:
I created a file components/ActiveRecord.php with the following code:
<?php
namespace app\components;
use Yii;
class ActiveRecord extends \yii\db\ActiveRecord {
public static function getDb() {
if (isset($_SESSION['userdb'])) {
$currentDB = $_SESSION['userdb'];
return Yii::$app->get($currentDB);
}
}
}?>
So... on login I save the database on the session and in the aforementioned file which extends the default ActiveRecord I override the getDb function and I choose my own. Subsequently, I changed all models so they extend my ActiveRecord.
I am not sure that this strategy is correct but it almost works.
Where is the problem:
Everything works fine except from... RBAC! For RBAC I use the yii2-rbac extension. The problem is that the user is not getting his role from his database but from the first declared database in the config/web.php file. So, whatever the logged in user, the first db is used.
Question(s):
How can I solve this problem? Do you have any idea on where is the file that gives the role to the logged in user?
Bonus Questions: Do you think this strategy is wrong? If so, what would you suggest?
Thanks a lot in advance for your time and your support.
Question # 1: How can I solve this problem? Do you have any idea on where is the file that gives the role to the logged in user?
I would suggest you use yii2 admin extension for this purpose(https://github.com/mdmsoft/yii2-admin) that will solve your issue and it is the best extension to manage user role. use this link for better understanding (https://github.com/mdmsoft/yii2-admin/blob/master/docs/guide/configuration.md)
Install above by following above URL or just add "mdmsoft/yii2-admin": "~2.0" in your composer.json file and run composer update.
After successfully installed this extension run migration to create RBAC tables, if you already have then skipped it.
yii migrate --migrationPath=#yii/rbac/migrations
You have to do some configuration in your main.php to tell your application about public routes and for all other application route system will implement RBAC on them.
This is what you have to add in your main.php file.
'components' => [],
'as access' => [
'class' => 'mdm\admin\components\AccessControl',
'allowActions' => [
'site/login', // add or remove allowed actions to this list
'site/logout',
'site/error',
'site/index',
]
]
Above settings will tell your application to make the login, logout, error and index function are public under site controller and all other applications routes need to have RBAC assignment to perform the action.
main.php is exist in backend/config/main.php in my case, you can use according to your requirements may be in common/config/main.php
Question # 2: Bonus Questions: Do you think this strategy is wrong? If so, what would you suggest?
I think your approach is not extendable, I suggest you create a master database and use that for your application tenants with their databases.
Setup tenant table in master database with tenant name, database required parameters
Make master database connection in your application, that is used to get all tenant and there store DB connections from the master database.
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=master-db',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
]
Now at login page show tenants list to choose their info OR you can also play with the domain name as well if you are using the unique domain for each tenant. if you are using unique domain then you have to store domain name with tenant info in the master database to able to get tenant info based on the domain.
// Set Session Values on success login page OR read user domain and get tenant info from the master database and add into session.
``
$tenant = Tenant::findOne(Yii::$app->request->post('tenant_id'));
Yii::$app->session->set('DB_HOST', $tenant->DB_host);
Yii::$app->session->set('DB_USER', $tenant->DB_username);
Yii::$app->session->set('DB_PASS', $tenant->DB_password);
Yii::$app->session->set('DB_NAME', $tenant->DB_name);
``
Once you have tenant info you can get the tenant from the master database and add their database settings into SESSION.
Create a BaseModel.php class and extend it with all of your application models classes.
In you BaseModel.php add below method to make runtime DB connection based on domain or tenant selection from the login page.
public static function getDb(){
if ( Yii::$app->session->get('DB_HOST') ){
$host = Yii::$app->session->get('DB_HOST');
$dbName = Yii::$app->session->get('DB_NAME');
$dsn = "mysql:host=$host;dbname=$dbName";
Yii::$app->db->dsn = $dsn;
Yii::$app->db->username = Yii::$app->session->get('DB_USER');
Yii::$app->db->password = Yii::$app->session->get('DB_PASS');
return Yii::$app->db;
}
}
By doing this you will have the option to add multiple tenants from backend to master database and that will automatically available in your application.
I believe that this information is helpful for you.
Cheers!!!
Here is the documentation on dbmanager.
If the you want to get rbac info from a different database, this should work.
Inside your config folder, create a file called rbacdb.php. Add the following:
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=<db host or ip>;dbname=<dbname>',
'username' => '<dbuser>',
'password' => '<dbPassword>',
'charset' => 'utf8',
];
Then go to your config file and find the authmanger section. It should look like this:
'authManager' => [
'class' => 'yii\rbac\DbManager',
'db'=> require(__DIR__ . '/rbacdb.php'),
'assignmentTable'=>'<tableName>',
'itemChildTable'=>'<tableName>',
'itemTable'=>'<tableName>',
'ruleTable'=>'<tableName>'
]
EDIT: after rereading your post...each user doesn't need their own rbac table/schema. use the system that Yii has in place.

Drupal 8 - multisite with shared tables (user tables / all tables)?

I need to create about 20-30 Drupal8 sites on different domains. There will be similar content (difference only in details like city name, ajax calls, etc.) but also there will be a specific content like news.
I know all weakness of this idea, but anyway I think that shared tables in one database will be the best solution for this project.
My steps:
installing first default site (sites/default) with prefix for tables default_
creating directory for second site (sites/second), and configuring sites.php (seconddomain.com => sites/second)
installing second site (sites/second) with prefix for tables second_
... then I tried to use solution which is described on many sites:
$databases['default']['default'] = array(
'database-configuration-stuff' => '[...database configuration]'
'prefix' => array(
'default' => 'second_', // default prefix for second site
'users' => 'default_', // shared users...
'sessions' => 'default_',
'role' => 'default_',
'authmap' => 'default_',
),
);
but it doesn't work. I see only users from second site. Cache cleaning doesn't change anything. Any ideas?
Maybe there is possibility to create multi-page solution with one shared database (not only for users but for nodes also) and create content directed to different domains from one admin console?
BTW: If there is any possibility to create sth like this using Drupal7 I can change d8 to d7.
if you'd like to make sth I was looking for you've got three options:
you need to write your own module ;) ,
you need to wait for "Domain Access" module for D8: https://www.drupal.org/project/domain ,
you can also use D7 and module from URL which I provide above.
I chose 3rd option.

Find call returning outdated info immediately after Save

In my application, an action takes some user generated input and uses it to update an entry in the database.
The relevant code resembles the following:
$this->Model->save($data);
$result = $this->Model->findById($id);
The problem is that the contents of $result are outdated. That is, $result contains the record as it was before the save.
I'm assuming that the entry just isn't updated until the function returns. However, I can't do the obvious
$result = $this->Model->save($data);
because this method does not preserve relationships. In this case, the model belongs to another model. I need to be able to get the updated record and the record it belongs to.
See if
$result = $this->Model->findById($id);
is loading from the cache, not the database.
Take a look at:
http://fredericiana.com/2007/09/05/cakephp-delete-cached-models-on-database-change/
http://snook.ca/archives/cakephp/delete_cached_models/
Personally, I've never liked Cake's magic find functions. I find them difficult to add extra conditions/joins/etc to and I much prefer to type a few more lines and get something specific that I can easily adjust.
Sounds in this case like the magic find function is returning from the cache, and you should try a regular find call instead:
$this->Model->save($data);
$result = $this->Model->find('first', array('conditions' => array('id' => $id)));
However, you should try and narrow down your problem and find out exactly what it is for future reference. You could try manually clearing the cache before you do your magic find and see if you get the correct response:
$this->Model->save($data);
Cache::clear();
$result = $this->Model->findById($id);
Another unlikely yet possible option is that your $id variable may not be pointing to the correct model row, and your save might be creating a new one so that when you findById($id) the $id is different to the ID of the row you've just saved/created. In this case, it's worth doing a debug($id) or $this->log('ID is: ' . $id, 'my_test_log'); before and after your safe or at intervals through your code to work out what is happening and where.

Magento issuing ALTER TABLE command at every page load multiple times

I'm trying to track down anything I can to optimize the performance of my Magento site. I've just noticed that a certain SQL command is running UP TO 91 times on a page:
ALTER TABLE `enterprise_sales_order_grid_archive` MODIFY COLUMN `is_edited` smallint NOT NULL default '0' COMMENT ''
Does anyone have an idea what this is or why this is or most importantly., how I can fix it?
I have been getting similar issue and fix which I have applied is as follows :
This module is particularly active on any order events please disable it totally if you can.
if you look at : /app/code/core/Enterprise/SalesArchive/etc/config.xml
there is a cron job that and many event that is the main cause this to happend
please disabled all the event listening in the config.xml of this module.
Please try to comment ( If you have this file )
app/code/community/MDN/AdvancedStock/Block/Adminhtml/Sales/Order/Grid.php
line 69 to 79 in this way
protected function _prepareColumns()
{
parent::_prepareColumns();
$this->addAfterColumn('increment_id', array(
'header'=> Mage::helper('Organizer')->__('Organizer'),
'renderer' => 'MDN_Organizer_Block_Widget_Column_Renderer_Comments',
'align' => 'center',
'entity' => 'order',
'filter' => false,
'sortable' => false
),'real_order_id');
/*$this->addAfterColumn('payment_validated', array(
'header'=> Mage::helper('AdvancedStock')->__('Payment validated'),
'width' => '40px',
'index' => 'payment_validated',
'align' => 'center',
'type' => 'options',
'options' => array(
'1' => Mage::helper('purchase')->__('Yes'),
'0' => Mage::helper('purchase')->__('No'),
),
),'status');*/
//raise event to allow other modules to add columns
Mage::dispatchEvent('salesorder_grid_preparecolumns', array('grid'=>$this));
}
Another option to fix this issue is as follows
Basically we get two flags which might be added to a app/etc/local.xml. Assuming a site is hosted on multiple frontend web servers, update scripts should only be processed on one of them. Setting
<global>
<skip_process_modules_updates>1</skip_process_modules_updates>
</global>
on all but the “master” web server will avoid race conditions between the hosts.
There is a better way to avoid race conditions for processing setup scripts, and that is by not allowing any web servers at all to process setup scripts.
This is how it works. Instead of setting the skip_process_modules_updates flag on all but one server, it should be set to a true value on all hosts. Then the setup process can be triggered by a short shell script.
It works for me. See If that helps to you.

Exposing table name and field names in request URL

I was tasked to create this Joomla component (yep, joomla; but its unrelated) and a professor told me that I should make my code as dynamic as possible (a code that needs less maintenance) and avoid hard coding. The approach we thought initially is take url parameters, turn them into objects, and pass them to query.
Let's say we want to read hotel with id # 1 in the table "hotels". lets say the table has the fields "hotel_id", "hotel_name" and some other fields.
Now, the approach we took in making the sql query string is to parse the url request that looked like this:
index.php?task=view&table=hotels&hotel_id=1&param1=something&param2=somethingelse
and turned it into a PHP object like this (shown in JSON equivalent, easier to understand):
obj = {
'table':'hotel',
'conditions':{
'hotel_id':'1',
'param1':'something',
'param2':'somethingelse'
}
and the SQL query will be something like this where conditions are looped and appended into the string where field and value of the WHERE clause are the key and value of the object (still in JSON form for ease):
SELECT * FROM obj.table WHERE hotel_id=1 AND param1=something and so on...
The problem that bugged me was the exposing of the table name and field names in the request url. I know it poses a security risk exposing items that should only be seen to the server side. The current solution I'm thinking is giving aliases to each and every table and field for the client side - but that would be hard coding, which is against his policy. and besides, if I did that, and had a thousand tables to alias, it would not be practical.
What is the proper method to do this without:
hard coding stuff
keep the code as dynamic and adaptable
EDIT:
Regarding the arbitrary queries (I forgot to include this), what currently stops them in the back end is a function, that takes a reference from a hard-coded object (more like a config file shown here), and parses the url by picking out parameters or matching them.
The config looks like:
// 'hotels' here is the table name. instead of parsing the url for a table name
// php will just find the table from this config. if no match, return error.
// reduces risk of arbitrary tables.
'hotels' => array(
// fields and their types, used to identify what filter to use
'structure' => array(
'hotel_id'=>'int',
'name'=>'string',
'description'=>'string',
'featured'=>'boolean',
'published'=>'boolean'
),
//these are the list of 'tasks' and accepted parameters, based on the ones above
//these are the actual parameter names which i said were the same as field names
//the ones in 'values' are usually values for inserting and updating
//the ones in 'conditions' are the ones used in the WHERE part of the query
'operations' =>array(
'add' => array(
'values' => array('name','description','featured'),
'conditions' => array()
),
'view' => array(
'values' => array(),
'conditions' => array('hotel_id')
),
'edit' => array(
'values' => array('name','description','featured'),
'conditions' => array('hotel_id')
),
'remove' => array(
'values' => array(),
'conditions' => array('hotel_id')
)
)
)
and so, from that config list:
if a parameters sent for a task is not complete, server returns an error.
if a parameter from the url is doubled, only the first parameter read is taken.
any other parameters not in the config are discarded
if that task is not allowed, it wont be listed for that table
if a task is not there, server returns an error
if a table is not there, server returns an error
I actually patterned this after seeing a component in joomla that uses this strategy. It reduces the model and controller to 4 dynamic functions which would be CRUD, leaving only the config file to be the only file editable later on (this was what I meant about dynamic code, I only add tables and tasks if further tables are needed) but I fear it may impose a security risk which I may have not known yet.
Any ideas for an alternative?
I have no problem with using the same (or very similar) names in the URL and the database — sure, you might be "exposing" implementation details, but if you're choosing radically different names in the URL and the DB, you're probably choosing bad names. I'm also a fan of consistent naming — communication with coders/testers/customers becomes much more difficult if everyone calls everything something slightly different.
What bugs me is that you're letting the user run arbitrary queries on your database. http://.../index.php?table=users&user_id=1, say? Or http://.../index.php?table=users&password=password (not that you should be storing passwords in plaintext)? Or http://.../index.php?table=users&age=11?
If the user connected to the DB has the same permissions as the user sitting in front of the web browser, it might make sense. Generally, that's not going to be the case, so you'll need some layer that knows what the user is and isn't allowed to see, and that layer is a lot easier to write correctly by whitelisting.
(If you've stuck enough logic into stored procedures, then it might work, but then your stored procedures will hard-code column names...)
When composing a SQL query with data from the input, it presents a security risk. But keep in mind that columns values are inserted to the fields by taking input from the user, analyzing it and composing a SQL query with it (except for prepared statements). So when done properly, you have nothing to worry about - simply restrict the user to those column & tables. Open source software's code/database is visible to all - and it doesn't harm the system so much as one would think.
Your aliasses could be a rot13() on the meta/name of your objects.
Although, if you escape the input accordingly when working with those names, I don't see any problem in exposing their names.